UINavigation controller: present and dismiss programmatically - swift

I have a TableViewController which I want to present modally and I need it to have a NavigationBar.
To get that navbar, I have an embedded UINavigationController and as far as I know, that UINavigationController is what I have to present modally, so that's what I've done.
Everything works just fine, but I can't manage to dismiss that controller properly. Here is what I've got so far:
func presentErrorMessages(errorMessages: [String]) {
let storyBoard: UIStoryboard = UIStoryboard(name: "Message", bundle: nil)
let infoMessagesNavigationViewController = storyBoard.instantiateViewController(withIdentifier: "InfoMessagesNavigation") as! ModalNavigationController
let infoMessagesTableViewController = infoMessagesNavigationViewController.viewControllers[0] as! InfoMessagesTableViewController
infoMessagesTableViewController.errorMessages = errorMessages
self.navigationController?.present(infoMessagesNavigationViewController, animated: true)
}
I use that to present ModalNavigationController, and this to dismiss it:
class ModalNavigationController: BaseNavigationController {
var backNavItem = UINavigationItem()
var okNavItem = UINavigationItem()
override func viewDidLoad() {
super.viewDidLoad()
let backButton = UIBarButtonItem(title: "Back", style: .plain, target: self, action: #selector(dismissModal))
backNavItem.leftBarButtonItem = backButton
...
var items = [UINavigationItem]()
items.append(backNavItem)
self.navigationBar.items = items
}
#objc func dismissModal() {
self.dismiss(animated: true)
}
}
When I press that back button, there is no change but the navbar which gets blank (with no title). I have the feeling that the application just 'forgets' what is the NavigationController used before the new one is presented.
How can I solve this?

Try something like this:
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Back", style: .done, target: self, action: #selector(dismissModal))
...
}
#objc func dismissModal() {
self.dismiss(animated: true, completion: nil)
}

I managed to solve the problem by placing and invoking the dismissfunction on my TableViewController rather than my NavigationController:
...
public func setBackButton(){
if self.navigationController != nil {
let item = UIBarButtonItem(title: "Back", style: .plain, target: self, action: #selector(dismissModal))
self.navigationItem.leftBarButtonItem = item
}
}
#objc func dismissModal() {
self.dismiss(animated: true)
}

Related

UIButton is only responsive sometimes when pressing down [duplicate]

I have a UIBarButtonItem in the right side of my navigation that has an image of a gear and presents my settings view controller. I can get it to work properly when I create the button in setupNavigationBar(), but it doesn't work if I create the button as a property. I can't wrap my head around what would be different about these two scenarios. The button is present in both situations, but the functionality isn't.
This version doesn't work
class DecksController: UIViewController {
let settingsBarButton: UIBarButtonItem = {
let barButton = UIBarButtonItem(image: #imageLiteral(resourceName: "settings"), style: .plain, target: self, action: #selector(presentSettings))
return barButton
}()
override func viewDidLoad() {
super.viewDidLoad()
setupNavigationBar()
}
#objc func presentSettings() {
let settingsController = SettingsController()
self.navigationController?.pushViewController(settingsController, animated: true)
}
func setupNavigationBar() {
self.navigationItem.rightBarButtonItem = settingsBarButton
}
}
This version does work
class DecksController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupNavigationBar()
}
#objc func presentSettings() {
let settingsController = SettingsController()
self.navigationController?.pushViewController(settingsController, animated: true)
}
func setupNavigationBar() {
let settingsBarButton = UIBarButtonItem(image: #imageLiteral(resourceName: "settings"), style: .plain, target: self, action: #selector(presentSettings))
self.navigationItem.rightBarButtonItem = settingsBarButton
}
}
As you've discovered, it makes a big difference where this line occurs:
let barButton = UIBarButtonItem(image: #imageLiteral(resourceName: "settings"),
style: .plain, target: self, action: #selector(presentSettings))
The problem is the target:self part. When the bar button item is configured as part of an instance property initializer (your first example), the instance doesn't exist yet — it is what we are initializing. So self has no meaning, and the button ends up with no target. Therefore, tapping the button does nothing.
(Actually, to be quite technical, self is the class, but that's not a helpful thing to know.)
In your second example, that line is part of viewDidLoad, which runs considerably after the view controller instance has come into existence and has been initialized. viewDidLoad is an instance method, in fact. So self is the instance, as you expect.

UIBarButtonItem doesn't work when created as a property, but does when created in a function

I have a UIBarButtonItem in the right side of my navigation that has an image of a gear and presents my settings view controller. I can get it to work properly when I create the button in setupNavigationBar(), but it doesn't work if I create the button as a property. I can't wrap my head around what would be different about these two scenarios. The button is present in both situations, but the functionality isn't.
This version doesn't work
class DecksController: UIViewController {
let settingsBarButton: UIBarButtonItem = {
let barButton = UIBarButtonItem(image: #imageLiteral(resourceName: "settings"), style: .plain, target: self, action: #selector(presentSettings))
return barButton
}()
override func viewDidLoad() {
super.viewDidLoad()
setupNavigationBar()
}
#objc func presentSettings() {
let settingsController = SettingsController()
self.navigationController?.pushViewController(settingsController, animated: true)
}
func setupNavigationBar() {
self.navigationItem.rightBarButtonItem = settingsBarButton
}
}
This version does work
class DecksController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
setupNavigationBar()
}
#objc func presentSettings() {
let settingsController = SettingsController()
self.navigationController?.pushViewController(settingsController, animated: true)
}
func setupNavigationBar() {
let settingsBarButton = UIBarButtonItem(image: #imageLiteral(resourceName: "settings"), style: .plain, target: self, action: #selector(presentSettings))
self.navigationItem.rightBarButtonItem = settingsBarButton
}
}
As you've discovered, it makes a big difference where this line occurs:
let barButton = UIBarButtonItem(image: #imageLiteral(resourceName: "settings"),
style: .plain, target: self, action: #selector(presentSettings))
The problem is the target:self part. When the bar button item is configured as part of an instance property initializer (your first example), the instance doesn't exist yet — it is what we are initializing. So self has no meaning, and the button ends up with no target. Therefore, tapping the button does nothing.
(Actually, to be quite technical, self is the class, but that's not a helpful thing to know.)
In your second example, that line is part of viewDidLoad, which runs considerably after the view controller instance has come into existence and has been initialized. viewDidLoad is an instance method, in fact. So self is the instance, as you expect.

NavigationController doesn't see right bar button item

I'm trying to understand presentation view controllers by allowing the user to add new outcomes of a fortune telling app. When the user taps on "Add New Outcome" on the top right of the navigation bar a new view controller pops up and this is where the user can enter a new outcome. I'm trying to add a done button to the top right of the presentation controller but I'm getting an error saying Value of type 'UINavigationController?' has no member 'rightBarButtonItem'.
I thought it was because of referencing the topViewController was the problem but I'm doing this all in 1 ViewController.
#IBAction func actionButtonTapped(_ sender: UIBarButtonItem) {
let storyboard : UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let viewController = storyboard.instantiateViewController(withIdentifier: "AddNewOptionController")
viewController.modalPresentationStyle = .popover
let popover : UIPopoverPresentationController = viewController.popoverPresentationController!
popover.barButtonItem = sender
popover.delegate = self
present(viewController, animated: true, completion: nil)
}
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .fullScreen
}
func presentationController(_ controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
let navigationController = UINavigationController(rootViewController: controller.presentedViewController)
let doneButton = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(ViewController.dismissViewController))
//error below
navigationController.topViewController?.navigationController.rightBarButtonItem = doneButton
return navigationController
}
func dismissViewController() {
self.dismiss(animated: true, completion: nil)
}
Instead of:
navigationController.topViewController?.navigationController.rightBarButtonItem = doneButton
Try:
navigationController.topViewController?.navigationItem.rightBarButtonItem = doneButton
you can use this code:
self.navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(named:"YOUR_IMAGE_NAME"), style: .plain, target: self, action: #selector(YOUR_FUNCTION))
hope this work for you

Set table view into editing mode

I have a UITableView in a UIViewController and have added an edit button from code rather than IB. This comes with UITableViewControllers but not UIVCs. How can I get this button to put the table view into editing mode in swift? Thanks in advance for any help.
class WordsViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.rightBarButtonItem = self.editButtonItem()
}
func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
}
Here is a solution for Swift 4.2:
override func viewDidLoad() {
super.viewDidLoad()
// Use the edit button provided by the view controller.
navigationItem.rightBarButtonItem = editButtonItem
}
override func setEditing(_ editing: Bool, animated: Bool) {
// Takes care of toggling the button's title.
super.setEditing(editing, animated: true)
// Toggle table view editing.
tableView.setEditing(editing, animated: true)
}
The view controller's setEditing is called by default when the editButtonItem is pressed. By default, pressing the button toggles its title between "Edit" and "Done", so calling super.setEditing takes care of that for us, and we use the tableView's setEditing method to toggle the editing state of the table view.
Sources:
https://developer.apple.com/documentation/uikit/uiviewcontroller/1621471-editbuttonitem
https://developer.apple.com/documentation/uikit/uiviewcontroller/1621378-setediting
https://developer.apple.com/documentation/uikit/uitableview/1614876-setediting
Create rightBarButtonItem as below with an action.
In viewDidLoad() :
let rightButton = UIBarButtonItem(title: "Edit", style: UIBarButtonItemStyle.Plain, target: self, action: Selector("showEditing:"))
self.navigationItem.rightBarButtonItem = rightButton
and then make a function like,
func showEditing(sender: UIBarButtonItem)
{
if(self.tableView.isEditing == true)
{
self.tableView.isEditing = false
self.navigationItem.rightBarButtonItem?.title = "Done"
}
else
{
self.tableView.isEditing = true
self.navigationItem.rightBarButtonItem?.title = "Edit"
}
}
Make sure, : is appended to function name in Selector of action in viewDidLoad
Hope it helps!
Swift 3 & 4 answer that IMHO is better than other answers:
override func viewDidLoad() {
super.viewDidLoad()
let editButton = UIBarButtonItem(title: "Edit", style: .plain, target: self, action: #selector(toggleEditing)) // create a bat button
navigationItem.rightBarButtonItem = editButton // assign button
}
#objc private func toggleEditing() {
listTableView.setEditing(!listTableView.isEditing, animated: true) // Set opposite value of current editing status
navigationItem.rightBarButtonItem?.title = listTableView.isEditing ? "Done" : "Edit" // Set title depending on the editing status
}
Why do I think it's better:
Fewer code lines.
Bar button is initialized once but not every time you press the button.
Call this method on button click.
tableView.setEditing(true, animated: true)
Or if you want it to work like a toggle use
tableView.setEditing(!tableView.editing, animated: true)
I assume you have a button, which calls editButtonPressed on press. So implementation of this method could look like this.
override func viewDidLoad(){
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Edit", style: UIBarButtonItemStyle.Plain, target: self, action: Selector("editButtonPressed"))
}
func editButtonPressed(){
tableView.setEditing(!tableView.editing, animated: true)
if tableView.editing == true{
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Done", style: UIBarButtonItemStyle.Plain, target: self, action: Selector("editButtonPressed"))
}else{
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Edit", style: UIBarButtonItemStyle.Plain, target: self, action: Selector("editButtonPressed"))
}
}
This also changes title of the bar button.
Override the view controller's -setEditing:animated:, call super, and call the same method on your table view.
- (void)setEditing:(BOOL)editing animated:(BOOL)animated
{
[super setEditing:editing animated:animated];
[self.tableView setEditing:editing animated:animated];
}
First :
let editButton = UIBarButtonItem(title: "Edit", style: .plain, target: self, action: #selector(showEditing(_:)))
navigationItem.rightBarButtonItem = editButton
Then :
#objc func showEditing(_ sender: UIBarButtonItem)
{
if(self.tableView.isEditing == true)
{
self.tableView.isEditing = false
self.navigationItem.rightBarButtonItem?.title = "Edit"
}
else
{
self.tableView.isEditing = true
self.navigationItem.rightBarButtonItem?.title = "Done"
}
}
Swift 3.0 version of njuri post:
override func viewDidLoad() {
super.viewDidLoad()
PackageNameLabel.text = detailPackageName
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Edit", style: UIBarButtonItemStyle.plain, target: self, action: #selector(PackageDetailsTableViewController.editButtonPressed))
}
func editButtonPressed(){
tableView.setEditing(!tableView.isEditing, animated: true)
if tableView.isEditing == true{
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Done", style: UIBarButtonItemStyle.plain, target: self, action: #selector(PackageDetailsTableViewController.editButtonPressed))
}else{
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Edit", style: UIBarButtonItemStyle.plain, target: self, action: #selector(PackageDetailsTableViewController.editButtonPressed))
}
}
You only need 1 line of code in viewDidLoad() to get edit button and its related functionality.
navigationItem.rightBarButtonItem = editButtonItem

How change action BackButton in UISplitViewController?

I have Split View Controller.
I want change action back buttun on swift.
What me do?
Try this:
override func viewDidLoad() {
super.viewDidLoad()
var button = UIBarButtonItem(title: "YourNewButton", style: UIBarButtonItemStyle.Bordered, target: self, action: "doSomething")
self.navigationItem.leftBarButtonItem = button
}
func doSomething()
{
//do something
var vc = YourDestinationViewController()
self.presentViewController(vc, animated: true, completion: nil)
}