Change barButtonSystemItem after tapping - swift

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
}

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.

swift UITapGestureRecognizer not working on view

I had to create a new thread bcoz it's driving me crazy and all the other answers online are exactly the same. I have done this countless of times but I cannot see what I am missing for the life of me. I am using a "test" view controller just to get the tap gesture working but it isn't working at all... I am fairly certain that I am setting this up correctly, as this is how I've always implemented it in the past: (yes, I have checked the box for isUserInteractionEnabled). I am even implementing this on a different viewcontroller this exact way and it is working...
class TestViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.addGestureRecognizer(tap)
}
let tap = UITapGestureRecognizer(target: self, action: #selector(wasTapped))
#objc func wasTapped() {
print("tapped")
}
}
I have also tried adding the parameters to wasTapped:
#objc func wasTapped(gestureRecognizer: UITapGestureRecognizer) {
print("tapped")
}
You are saying:
override func viewDidLoad() {
super.viewDidLoad()
view.addGestureRecognizer(tap)
}
let tap = UITapGestureRecognizer(target: self, action: #selector(wasTapped))
The problem is the last line:
let tap = UITapGestureRecognizer(target: self, action: #selector(wasTapped))
You cannot just say let tap like that in the middle of nowhere. You are implicitly making an instance property. But you cannot initialize an instance property with a target of self, because self does not exist at the time an instance property is initialized. (I regard the fact that that code even compiles as a bug, and have reported it as such.)
Move that line to the start of viewDidLoad, like this:
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(wasTapped))
view.addGestureRecognizer(tap)
}
Try something like this
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(wasTapped(sender:)))
tap.numberOfTapsRequired = 1 // Default value
view.isUserInteractionEnabled = true
view.addGestureRecognizer(tap)
}
#objc func wasTapped(sender: UITapGestureRecognizer) {
print("tapped")
}
You have to enable interaction if you want to use gesture recognizers for standard UIView's
Add view.isUserInteractionEnabled = true in your viewDidLoad.
var tapGesture = UITapGestureRecognizer()
take a view and set IBOutlet like:
#IBOutlet weak var viewTap: UIView!
Write pretty code on viewDidLoad() like:
tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.myviewTapped(_:)))
tapGesture.numberOfTapsRequired = 1
tapGesture.numberOfTouchesRequired = 1
viewTap.addGestureRecognizer(tapGesture)
viewTap.isUserInteractionEnabled = true
this method is calling when tap gesture recognized:
#objc func myviewTapped(_ sender: UITapGestureRecognizer) {
if self.viewTap.backgroundColor == UIColor.yellow {
self.viewTap.backgroundColor = UIColor.green
}else{
self.viewTap.backgroundColor = UIColor.yellow
}
}

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

Swift - How to toggle between Play and Pause with UIBarButtonItem

I am trying with the below Swift code in my timer application to start and pause with the same button I have. But it is does not toggle. The button is set with Identifier as "Play" in the IDE. When I run the app, Timer starts and pauses correctly. But it does not toggle button. It always remain as "Play"
#IBAction func pressPausePlay(sender: AnyObject) {
if playPause == false
{
timer.invalidate()
self.playPauseButtonVar = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Play, target: self, action: "pressPausePlay")
playPause = true
}
else
{
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("updateTime"), userInfo: nil, repeats: true)
self.playPauseButtonVar = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Pause, target: self, action: "pressPausePlay")
playPause = false
}
}
You can use this Example scenarios to toggle between Play and Pause, for Button Titles, Image, Navigation Bar, ToolBar
import UIKit
import AVFoundation
class ViewController: UIViewController {
var myPlayer = AVAudioPlayer()
#IBOutlet var myNaviBar: UINavigationBar! //OUTLET TO NAVIGATION BAR IMPORTANT TO WORK WITH ITS BARBUTTON ITEMS
#IBOutlet var myToolBar: UIToolbar!
//**************START :Bottom LEFT TOOLBAR Bar Button Item***********/
#IBAction func toolBarItemPlay(sender: AnyObject) {
myToolBar.setItems([UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Pause, target: self, action: "toolBarItemPause:")], animated: true)
myPlayer.play()
}
#IBAction func toolBarItemPause(sender: AnyObject) {
myToolBar.setItems([UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Play, target: self, action: "toolBarItemPlay:")], animated: true)
myPlayer.pause()
}
//**************END :Bottom LEFT TOOLBAR Bar Button Item***********/
//**************START :Top LEFT Naivation Bar Button Item***********/
#IBAction func leftNavgationbarPlay(sender: UIBarButtonItem) {
myNaviBar.topItem?.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Pause, target: self, action: "leftNavigationbarPause:")
myPlayer.play()
}
//no need of control drag to this one below because we are calling from the above leftNavgationbarPlay's Selector/action
#IBAction func leftNavigationbarPause(sender : UIBarButtonItem) {
myNaviBar.topItem?.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Play, target: self, action: "leftNavgationbarPlay:")
myPlayer.pause()
}
//**************END :Top LEFT Naivation Bar Button Item***********/
//**************START :Top Right Naivation Bar Button Item***********/
#IBAction func naviPlay(sender: UIBarButtonItem) {
//change the topright navigation bar item from SystemItem Play '>' to systemitem pause '||'
// here I am assigning the top left item Play to Bar button item Pause
/* ACTION SELECTOR IMPORTANT. IT MUST CALL THE IB ACTION FUNCTION OF PAUSE AND VICEVERSA, WITH ":" */
myNaviBar.topItem?.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Pause, target: self, action: "naviPause:")
myPlayer.play()
}
#IBAction func naviPause(sender: UIBarButtonItem) {
myNaviBar.topItem?.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Play, target: self, action: "naviPlay:")
myPlayer.pause()
}
//**************END : Top Right Naivation Bar Button Item***********/
//************START :BUTTON IMAGE CHANGE - ">"to "||"************//
#IBAction func imagePlayButton(sender: UIButton) {
let chkImage = sender.currentImage!
if chkImage == UIImage(named: "play.png"){
sender.setImage(UIImage(named: "pause.png"), forState: .Normal)
myPlayer.play()
} else {
sender.setImage(UIImage(named: "play.png"), forState: .Normal)
myPlayer.pause()
}
}
//************END :BUTTON IMAGE CHANGE - ">"to "||"************//
//************START BUTTON TITLE CHANGE - PLAY to PAUSE************//
#IBAction func playButton(sender: UIButton) {
let chkTitle = sender.currentTitle
if chkTitle == "Play" {
sender.setTitle("Pause", forState: .Normal)
myPlayer.play()
} else {
sender.setTitle("Play", forState: .Normal)
myPlayer.pause()
}
}
//************END BUTTON TITLE CHANGE - PLAY to PAUSE************//
#IBAction func naviStop(sender: UIBarButtonItem) {
myPlayer.stop()
myPlayer.currentTime = 0
}
#IBOutlet var sliderUI: UISlider!
#IBAction func mySlider(sender: UISlider) {
myPlayer.volume = sliderUI.value
}
override func viewDidLoad() {
super.viewDidLoad()
let audioFilePath = NSBundle.mainBundle().pathForResource("ARRehman", ofType: "mp3")
//need NSURL from string
do {
myPlayer = try AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: audioFilePath!))
}
catch {
print("Some error")
}
}
}
You need to replace bar button items hold in UIToolbar after you assign a new instance to self.playPauseButtonVar.
If you have toolBar or navigationBar outlets:
self.toolBar.setItems([self.playPauseButtonVar], animated: true)
// Or
self.navigationBar.setItems([self.playPauseButtonVar], animated: true)
If your class inherits UIViewController:
self.setToolbarItems([self.playPauseButtonVar], animated: true)
// Or
self.navigationItem.setRightBarButtonItem(self.playPauseButtonVar, animated: true)
Yoichi, thanks for pointing me to the correct place. I removed the references on the Tool bar Outlet variable (bottomToolBar) under "Referencing Outlets". That resolved the error. However I am not able to add the tool bar outlet again, if I add, the error comes back!
it worked with me by creating bar button by code this is my code
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var navigationBar: UINavigationBar!
#IBOutlet weak var displayCountLabel: UILabel!
var buttonPause = UIBarButtonItem()
var buttonPlay = UIBarButtonItem()
var count=0
var timer=NSTimer()
override func viewDidLoad() {
super.viewDidLoad()
buttonPause = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Pause, target: self, action: "pauseAction")
buttonPlay = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.Play, target: self, action: "playAction")
self.navigationBar?.topItem?.leftBarButtonItem = buttonPlay
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func stopTimer(sender: AnyObject) {
timer.invalidate()
count = 0
displayCountLabel.text = "\(count)"
self.navigationBar?.topItem?.leftBarButtonItem = buttonPlay
}
func pauseAction() {
self.navigationBar?.topItem?.leftBarButtonItem = buttonPlay
timer.invalidate()
}
func playAction() {
self.navigationBar?.topItem?.leftBarButtonItem = buttonPause
timer=NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("displayCount"), userInfo: nil, repeats: true)
}
func displayCount()
{
count++
displayCountLabel.text = "\(count)"
}
}