UIButton is only responsive sometimes when pressing down [duplicate] - swift

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.

Related

InputAccessoryView and my problem (Swift)

I have such screen:
When you click on a textfield, the top of the keyboard should have "two arrows" on the left and the button "Done" on the right.
Like this:
I must say right away that I used Iqkeyboardmanager and because of this library there were problems therefore I had to remove it from the project. So help do it manually.
In the project, I already implemented the Done button, my code:
extension UITextField {
func addInputAccessoryView(title: String, target: Any, selector: Selector) {
let toolBar = UIToolbar(frame: CGRect(x: 0.0,
y: 0.0,
width: UIScreen.main.bounds.size.width,
height: 44.0))
let flexible = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let barButton = UIBarButtonItem(title: title, style: .plain, target: target, action: selector)
toolBar.setItems([flexible, barButton], animated: false)
self.inputAccessoryView = toolBar
}
}
final class ProfileSettingsViewController: UIViewController,UINavigationControllerDelegate, UIImagePickerControllerDelegate, UITextFieldDelegate {
#objc func tapDone() {
self.view.endEditing(true)
}
override func viewDidLoad() {
super.viewDidLoad()
self.nameTF.addInputAccessoryView(title: "Done", target: self, selector: #selector(tapDone))
self.companyTF.addInputAccessoryView(title: "Done", target: self, selector: #selector(tapDone))
self.nameTF.delegate = self
self.companyTF.delegate = self
.....
The rest of the code
}
}
What i have at the moment:
I need to add two black arrows on the left and make the Done buttons black. Help me please.
If you think my code is bad, you can provide your correct example. I am new in ios development.
1.Create a custom delegate which will notify the ViewController when some button is pressed inside the Input Accessory View.
protocol MyCustomTextFieldDelegate: class {
func doneButtonPressed()
func arrowDownPressed()
func arrowUpPressed()
}
2.Create a custom subclass of the the UITextField and make a function that will create the Input Accessory View. Define the newly created MyCustomTextFieldDelegate as a weak class property. (Note: we are making this subclass, as we are unable to add the MyCustomTextFieldDelegate as a property to the UITextField directly)
class MyCustomTextField: UITextField {
weak var customTextFieldDelegate: MyCustomTextFieldDelegate?
func enableInputAccessoryView() {
let toolBar = UIToolbar(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 50))
toolBar.barStyle = UIBarStyle.default
toolBar.isTranslucent = true
let space = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace, target: nil, action: nil)
let doneButton = UIBarButtonItem(title: "Done", style: .done, target: self,
action: #selector(textFieldDonePressed))
doneButton.tintColor = .black
let arrowDown = UIBarButtonItem(image: UIImage(named: "arrow-right"), style: .done, target: self, action: #selector(arrowUpPressed))
arrowDown.tintColor = .black
let arrowUp = UIBarButtonItem(image: UIImage(named: "arrow-right"), style: .done, target: self, action: #selector(arrowDownPressed))
arrowUp.tintColor = .black
toolBar.setItems([arrowUp, arrowDown, space, doneButton], animated: false)
toolBar.isUserInteractionEnabled = true
toolBar.sizeToFit()
inputAccessoryView = toolBar
}
#objc private func textFieldDonePressed() {
endEditing(true)
customTextFieldDelegate?.doneButtonPressed()
}
#objc private func arrowDownPressed() {
customTextFieldDelegate?.arrowDownPressed()
}
#objc private func arrowUpPressed() {
customTextFieldDelegate?.arrowUpPressed()
}
}
3.In your ViewController change your current UITextField to your new MyCustomTextField and apply the Input Accessory View.
yourTextField.enableInputAccessoryView()
4.Set your ViewController to be the MyCustomTextField delegate.
yourTextField.customTextFieldDelegate = self
5.Confrom your ViewController to the newly created MyCustomTextFieldDelegate, where you will handle the user interaction.
extension YourViewController: MyCustomTextFieldDelegate {
func doneButtonPressed() {
// Handle done Pressed
}
func arrowDownPressed() {
// Handle down pressed
}
func arrowUpPressed() {
// Handle up pressed
}
}

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.

Change barButtonSystemItem after tapping

I have a bar button in the right side of the screen, it's barButtonSystemItem: .edit and it's used to put tableView in the editing mode. I want when, user tap on it, it change to barButtonSystemItem: .done and it close tableview from editing mode.
Just to be clear, every time that barButton is clicked, the type of it should be change from edit to done.
Here is my code, but it always stay with edit, and not changing to done
fileprivate func addBarButton() {
if tableView.isEditing {
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(editButtonAction))
} else {
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(doneButtonAction))
}
}
#objc func editButtonAction(sender: UIBarButtonItem) {
tableView.isEditing = !tableView.isEditing
}
#objc func doneButtonAction(sender: UIBarButtonItem) {
tableView.isEditing = !tableView.isEditing
}
override func viewDidLoad() {
super.viewDidLoad()
addBarButton()
tableView?.isEditing = true
}
In the bodies of methods editButtonAction and editButtonAction you only change the tableView editing state by doing tableView.isEditing = !tableView.isEditing. This action does nothing to navigationItem at all.
I'd recommend you to refactor the code a bit by renaming addBarButton into updateBarButton and call it every time when the table editing state changes and additionally from viewDidLoad as you do now. So your code would become something like this:
fileprivate func updateBarButton() {
if tableView.isEditing {
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(editButtonAction))
} else {
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(doneButtonAction))
}
}
#objc func editButtonAction(sender: UIBarButtonItem) {
tableView.isEditing = !tableView.isEditing
updateBarButton() // Note the additional call of the update method
}
#objc func doneButtonAction(sender: UIBarButtonItem) {
tableView.isEditing = !tableView.isEditing
updateBarButton() // Note the additional call of the update method
}
override func viewDidLoad() {
super.viewDidLoad()
tableView?.isEditing = true
updateBarButton() // Note that we call this method after changing the table view state so that it will have the most recent value
}

UINavigation controller: present and dismiss programmatically

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)
}

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