Why isn't my UIBarButtonItem calling my custom function? - ios

I have a UIViewController with a UIBarButtonItem configured to call my custom function called func settingsTapped(sender: AnyObject?) in which I've put performSegueWithIdentifier. I've determined that this function isn't being called even though the button DOES work and the segue somehow still works because it goes to the correct view controller.
First VC:
class CalculatorViewController: UIViewController {
private let timeInterval = 0.016 //how frequently we change the displayed number
private let animationLength = 0.3
private var timer: NSTimer?
private var counter: Int = 0
private var max: Double = 0.0
var startingPreferredUnits: String?
#IBOutlet weak var weightLifted: UITextField!
#IBOutlet weak var repetitions: UITextField!
#IBOutlet weak var oneRepMax: UILabel!
#IBOutlet weak var percentages: UITextView!
#IBOutlet weak var units: UILabel!
override func viewDidLoad() {
let settingsButton = UIBarButtonItem(title: "Settings", style: .Plain, target: self, action: #selector(settingsTapped(_:)))
self.navigationItem.leftBarButtonItem = settingsButton
super.viewDidLoad()
}
func settingsTapped(sender: AnyObject?) {
startingPreferredUnits = UserDefaultsManager.sharedInstance.preferredUnits
print("In segue, units is \(startingPreferredUnits)") // never prints this caveman debugging
self.performSegueWithIdentifier("segueToSettings", sender: self)
}
}
In the storyboard, I placed a Bar Button Item in the nav bar:
I created a Show (e.g. Push) segue between the Settings bar button item and the Settings view controller and I've given this segue an identifier of 'segueToSettings`. When I touch the Settings button, it does present the Settings view controller, but it doesn't print my caveman debugging line to the console.
I also tried creating the segue between the CalculatorViewController itself and the SettingsViewController (which I think may even be a better way) but when I have it set up that way, nothing happens at all when I touch the Settings button.
I've tried everything I could find on SO but nothing has worked. I hope I don't earn a Stupid Question badge on this one.
UPDATE 1:
I'm still struggling with this one. Here's something else I've learned and tried that didn't work. Clicking the Settings button works by performing the segue from the button to the Settings page which I created in the Storyboard. As one would expect, it will call override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) before doing this. So it's completely ignoring the action: #selector(settingsTapped(_:)) part. Also, I can change the identifier of the segue in the storyboard and it makes no difference at all. It still works. I can even delete the identifier and it works.
I also tried adding another button (this time a barButtonSystemItem like so:
override func viewDidLoad() {
super.viewDidLoad()
let settingsButton = UIBarButtonItem(title: "Settings", style: .Plain, target: self, action: #selector(settingsTapped(_:)))
let saveButton = UIBarButtonItem(barButtonSystemItem: .Add, target: self, action: #selector(self.saveThisLift(_:)))
self.navigationItem.leftBarButtonItem = settingsButton
self.navigationItem.rightBarButtonItem = saveButton
}
func saveThisLift(sender: UIBarButtonItem) {
print("I'm in saveThisLift") // never prints
let weight = weightLifted.text
let reps = repetitions.text
let maxAmount = oneRepMax.text
let unitsText = units.description
coreDataStack.saveLiftEvent(currentLiftName!, formula: currentFormulaName!, weight: weight!, repetitions: reps!, maxAmount: maxAmount!, unitsUsed: unitsText)
}
func settingsTapped(sender: AnyObject?) {
startingPreferredUnits = UserDefaultsManager.sharedInstance.preferredUnits
print("In segue, units is \(startingPreferredUnits)") // never prints this caveman debugging
self.performSegueWithIdentifier("segueToSettings", sender: self)
}
Like the Settings button, touching it does nothing except make it flash. I should point out that I added the bar button items via the storyboard and I think that's unnecessary since I'm trying to add them programmatically. However, without them, neither of the buttons appears.

After a lot more research and more trial and error, I've figured it out. I learned a number of things that I'll leave here for anyone who has this problem in the future.
At a high-level, trying to do some of this in the storyboard and some in code made it easy to get confused. The key things for me were:
I wasn't dealing with a UINavigationController with its out-of-the-box root view controller, I was dealing with a UIViewController. With the UINavigationController, you don't have to do as much. But with a UIViewController, I had to add a UINavigationBar and to that, I had to add a single UINavigationItem. I did this by dragging them from the Object Library to my storyboard.
Don't try to put a UILabel in the UINavigationBar to do what I was trying to do, which was have them call custom functions. Trust me, it doesn't work, at least not in my case.
After figuring out I needed to add a UINavigationItem, the next ah-ha! moment for me was the fact that I could put multiple UIBarButtonItems in it (note: my example in my question shows just one to keep it simple but I'm actually adding three items)
The magic piece of the puzzle was connecting my code to the storyboard. I simply Ctrl-clicked from my UINavigationItem to my view controller and created an #IBOutlet called #IBOutlet weak var navItem: UINavigationItem!
My code that creates the buttons and adds them to the view uses this outlet like so (simplified):
_
class CalculatorViewController: UIViewController {
#IBOutlet weak var navItem: UINavigationItem!
override func viewDidLoad() {
super.viewDidLoad()
let settingsButton = UIBarButtonItem(title: "Settings", style: .Plain, target: self, action: #selector(self.segueToSettings(_:)))
let viewLogButton = UIBarButtonItem(title: "Log", style: .Plain, target: self, action: #selector(self.segueToLog(_:)))
let saveButton = UIBarButtonItem(barButtonSystemItem: .Add, target: self, action: #selector(self.saveLift(_:)))
self.navItem.leftBarButtonItem = settingsButton
self.navItem.rightBarButtonItems = [saveButton, viewLogButton]
}
func saveLift(sender: AnyObject) {
let weight = weightLifted.text
let reps = repetitions.text
let maxAmount = oneRepMax.text
let unitsText = units.text
coreDataStack.saveLiftEvent(currentLiftName!, formula: currentFormulaName!, weight: weight!, repetitions: reps!, maxAmount: maxAmount!, unitsUsed: unitsText!)
performSegueWithIdentifier("segueToLog", sender: self)
}
}
Lastly, when you create that #IBOutlet, don't name it navigationItem: UINavigationItem because that will make Xcode very unhappy:
I burned a lot of hours on this one. I hope this information helps somebody avoid that in the future.

Related

Swift, confusion with UIBarButtonItem

I began to learn Swift recently. When I tried to make my first App I got confused with UIBarButtonItem. If I put let UIBarButtonItem initialization outside the viewDidLoad() function, nothing happens when I press the Next Button.
class ViewController: UIViewController, UITextFieldDelegate {
let rightBarButton: UIBarButtonItem = UIBarButtonItem(title: "Next", style: .plain, target: self, action: #selector(onClickNext(button:)))
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
self.navigationItem.rightBarButtonItem = rightBarButton
}
func onClickNext(button: UIBarButtonItem) {
print("should push view controller")
}
}
However, when I put the initialization into the viewDidLoad() function, the output area does output the sentense that I set in the onClickNext(button:) function.
class ViewController: UIViewController, UITextFieldDelegate {
var rightBarButton: UIBarButtonItem?
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
self.rightBarButton = UIBarButtonItem(title: "Next", style: .plain, target: self, action: #selector(onClickNext(button:)))
self.navigationItem.rightBarButtonItem = rightBarButton
}
func onClickNext(button: UIBarButtonItem) {
print("should push view controller")
}
}
Also, I I found that when I put the initialization outside the viewDidLoad() function, and I add a UITextField to viewController, the rightBarButton works if I touch the textfield before I press the button.
That make me confused. What is the mechanism?
Well, maybe you are missing how a ViewController works inside.
First, viewDidLoad is the area were you usually setup or initialize any view or properties of the view. This method is also called only once during the life of the view controller object. This means that self already exists.
Knowing this, is important to understand what a let property does, (from Apple)
A constant declaration defines an immutable binding between the constant name and the value of the initializer expression; after the value of a constant is set, it cannot be changed. That said, if a constant is initialized with a class object, the object itself can change, but the binding between the constant name and the object it refers to can’t.
Even though the upper area is where you declare variables and constants, is usually meant for simple initialization, it's an area for just telling the VC that there is an object that you want to work with and will have a class global scope, but the rest of functionality will be added when the view hierarchy gets loaded (means that the object does not depends of self, for example, when adding target to a button, you are referring to a method inside of self)....this variables or constants are called Stored Properties
In its simplest form, a stored property is a constant or variable that is stored as part of an instance of a particular class or structure. Stored properties can be either variable stored properties (introduced by the var keyword) or constant stored properties (introduced by the let keyword).
And finally, you have a lazy stored property that maybe can be applied for what you want:
A lazy stored property is a property whose initial value is not calculated until the first time it is used. You indicate a lazy stored property by writing the lazy modifier before its declaration.
Solution: create a lazy var stored property or add his properties inside ViewDidLoad (when self already exists)
lazy private var doneButtonItem : UIBarButtonItem = {
[unowned self] in
return UIBarButtonItem(title: "Next", style:UIBarButtonItemStyle.Plain, target: self, action: #selector(onClickNext(button:)))
}()
OR
let rightBarButton: UIBarButtonItem?
override func viewDidLoad() {
super.viewDidLoad()
rightBarButton = UIBarButtonItem(title: "Next", style: .plain, target: self, action: #selector(onClickNext(button:)))
}
import UIKit
class ViewController: UIViewController {
var btnName = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
btnName.setImage(UIImage(named: "imagename"), for: .normal)
btnName.frame = CGRect(x:0,y: 0,width: 30,height: 30)
btnName.addTarget(self, action: #selector(addTargetActionForButton(:_)), for: .touchUpInside)
let rightBarButton = UIBarButtonItem(customView: btnName)
self.navigationItem.rightBarButtonItem = rightBarButton
}
func addTargetActionForButton(_ sender : UIButton){
}
}
Your are taking let variable out side of viewDidLoad You can not take let global variable. if you want to take let than you need to declare inside viewDidLoad. Fore more info let and var check this link What is the difference between `let` and `var` in swift?
override func viewDidLoad() {
let rightBarButton: UIBarButtonItem = UIBarButtonItem(title: "Next", style: .plain, target: self, action: #selector(onClickNext(button:)))
super.viewDidLoad()
self.view.backgroundColor = .white
self.navigationItem.rightBarButtonItem = rightBarButton
}
UIBatButtonItem may contain UIButton
above example image have UINavigationItem which contains LeftBarButtonItems ([UIBarButton]), and RightBarButtonItems([UIBarButtonItem]), each UIBarButtonItem contain UIButton
we can customise the UIButton to specify how to display in View
And we can connect button action directly to the UIButton

Add a button to the native toolbar

I have the following code
final class MyViewController: UIViewController,
UICollectionViewDataSource, UICollectionViewDelegate, /*..*/ {
//
// Declaring local variables, other controllers and delegates etc.
//
override func viewDidLoad() {
super.viewDidLoad()
let item = UIBarButtonItem(title: "A button that should do nothing",
style: .Plain, target: nil, action: nil)
self.navigationController!.toolbar.items = [item]
self.navigationController!.toolbarHidden = false
}
// the rest of my code
}
And no matter what I do, the toolbar appears, yet nothing will render inside of it.
I also tried
self.navigationController?.toolbarItems?.insert(item, atIndex: 0)
to do the insertion of the item, but still no luck.
What am I doing wrong?
Fixed by adding items via
self.toolbarItems = [item]

UIViewController & UITableViewController - Settings Page

I've made a little app on the UIViewController and I want it to have a settings page.
I made a button on it which leads to a UITableView Controller, so far all I have on it is a toggle for Vibrate but when I turn the switch off, go to main, and return back the switch is reset to original position ('on').
How can I make it so the state stays and where do I define the action for the toggle?
You should at start, load the UI with the settings current values.
In the viewWillAppear or even in viewDidLoad, you can just set the options, it will make the view shows the data as is set.
I make a simple project to demonstrate how to save the switch state. Here's the sample code and you can download the whole project here: https://drive.google.com/file/d/0B2_OK50NaRBpVVE4ZzhORUJJaG8/view?usp=sharing
class ViewController: UIViewController {
#IBOutlet weak var mySwitch: UISwitch!
#IBOutlet weak var stateLabel: UILabel!
let defaults = NSUserDefaults.standardUserDefaults()
let savedKey = "switch_state"
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Clear", style: .Plain, target: self, action: "clearSettings")
mySwitch.addTarget(self, action: "toggleSwitch", forControlEvents: .ValueChanged)
// Retrieve data from NSUserDefaults & update switch state and label
let switchState = defaults.boolForKey(savedKey)
mySwitch.setOn(switchState, animated: true)
self.stateLabel.text = "Current state: \(mySwitch.on)"
}
func toggleSwitch() {
// Each time we toogle switch, save switch state
defaults.setBool(mySwitch.on, forKey: savedKey)
defaults.synchronize()
// Update label
self.stateLabel.text = "Current state: \(mySwitch.on)"
}
func clearSettings() {
// Remove saved switch state
defaults.removeObjectForKey(savedKey)
}
}

Memory usage increases with every segue to modal vc

I have two view controllers, one is the timeline the second one is for the creation. In that second view controller I have a sub view. This sub view is an SKView. Now every time I segue to it, it increases the memory usage by 2mb (on a real device), but the memory usage stays the same when I unwind it.
So it is like this: I start with a usage of 12mb, then it gets 14-15mb. After the unwind it stays around 14-15mb. After the second segue to it, it becomes 17mb... and so on.
This is the code used in the timeline controller:
#IBAction func createButtonAct(sender: AnyObject) {
self.performSegueWithIdentifier("create", sender: self)
}
#IBAction func unwindFromCreation(segue: UIStoryboardSegue) {
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "create"{
let vc = segue.destinationViewController as! CreateViewController
if vc.currCountry == nil || vc.currCountry != self.currCountry{
vc.currCountry = self.currCountry
}
}
}
And this is the code in the create View Controller:
class CreateViewController: UIViewController, UITextViewDelegate {
#IBOutlet weak var bubbleView: SKView!
#IBOutlet var arrow: UIButton!
var ref: Firebase!
let categories = CatsAndColors.categories
#IBOutlet var doneButton: UIButton!
#IBOutlet var titleField: UITextField!
#IBOutlet var descriptionView: KMPlaceholderTextView!
var choosedCat: String!
var selectedCats: NSMutableArray!
var currCountry:String!
var tap: UITapGestureRecognizer!
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(true)
UIApplication.sharedApplication().statusBarStyle = .Default
UIView.animateWithDuration(0.5, animations: { () -> Void in
self.arrow.transform = CGAffineTransformMakeRotation(3.14159)
})
titleField.addTarget(self, action: "textFieldDidChange:", forControlEvents: UIControlEvents.EditingChanged)
titleField.addTarget(self, action: "textFieldDidBegin:", forControlEvents: UIControlEvents.EditingDidBegin)
titleField.addTarget(self, action: "textFieldDidEnd:", forControlEvents: UIControlEvents.EditingDidEnd)
// the targets get removed in viewWillDisappear
selectedCats = NSMutableArray()
}
override func viewDidLoad() {
super.viewDidLoad()
ref = Firebase(url: "https://blabber2.firebaseio.com")
self.doneButton.enabled = false
doneButton.setBackgroundImage(UIImage(named: "Done button inactive"), forState: .Disabled)
doneButton.setTitleColor(UIColor(netHex: 0xF6F6F6), forState: .Disabled)
doneButton.setTitleColor(UIColor.whiteColor(), forState: .Normal)
self.setupBubbles()
self.descriptionView.delegate = self
}
func setupBubbles(){
let floatingCollectionScene = ChooseBubblesScene(size: bubbleView.bounds.size)
// floatingCollectionScene.scaleMode = .AspectFit
/*let statusBarHeight = CGRectGetHeight(UIApplication.sharedApplication().statusBarFrame)
let titleLabelHeight = CGRectGetHeight(self.tibleLabel.frame)*/
bubbleView.presentScene(floatingCollectionScene)
for (category, color) in categories {
let node = ChooseBubbleNode.instantiate()
node!.vc = self
node!.fillColor = SKColor(netHex: color)
node!.strokeColor = SKColor(netHex: color)
node!.labelNode.text = category
floatingCollectionScene.addChild(node!)
}
}
...
And the catsAndColors struct looks like this:
struct CatsAndColors{
static var categories = ["Crime":0x5F5068, "Travel":0xFBCB43, "Religion":0xE55555, "Tech":0xAF3151, "Economy":0x955BA5, "Games":0xE76851, "Climate":0x6ED79A, "Books":0xE54242, "History":0x287572, "Clothes":0x515151, "Sports":0x4AB3A7, "Food":0xD87171, "Politics":0x5FA6D6, "Music":0xDD2E63, "Tv-shows":0x77A7FB]
}
Maybe you have created some sort of retain cycle between your view controllers.
If both view controllers hold a reference to each other, then try declaring one of the references as weak.
For more information on the topic read Resolving Strong Reference Cycles Between Class Instances.
I solved the problem, it was strong reference in the sknode file.
Thank you for your answers.

How do I set title for UIBarButtonItem?

I'm a beginner iPhone developer.
How can I programmatically set the title for the UIBarButtonItem?
My code is the following:
self.navigationItem.rightBarButtonItems =
UIBarButtonItem(
barButtonSystemItem: .Cancel, target: self,
action: "barButtonItemClicked:")
#IBAction func barButtonItemClicked(sender: UIBarButtonItem) {
//print something
}
Use different initialiser that allows you to specify the title:
UIBarButtonItem(title: "title", style: .Plain, target: self, action: "barButtonItemClicked:")
Swift 3.1 Update
UIBarButtonItem(title: "title", style: .plain, target: self, action: #selector(barButtonItemClicked))
Swift 2 answer
You could just add the following to viewDidLoad:
// Some text
self.barButtonItemClicked.title = "Right Button!"
OR
// A Unicode Gear Symbol
// See: http://graphemica.com/%E2%9A%99
self.barButtonItemClicked.title = "\u{2699}"
The ViewController.Swift code below would set the name barButtonItemClicked you used. I used a UIBarButtonItem to show how to set the title. But it is trivial to adapt it for a function.
import UIKit
class ViewController: UIViewController {
// Please make sure the line below is properly connected to your Storyboard!
#IBOutlet weak var barButtonItemClicked: UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// Set the text of the NavBarButtonItem Title
self.barButtonItemClicked.title = "Right Button!"
// Gear Icon
// self.barButtonItemClicked.title = "\u{2699}"
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Swift 4:
#IBOutlet var saveButton: UIBarButtonItem!
saveButton.title = "Saved"
In my case, I wanted to toggle between the Edit|Done.
However, I couldn’t use the leftBarButtonItem because I already had another UIBarButtonItem.
What I did is the following:
1- Create #IBOutlet weak var edit: UIBarButtonItem!
2- Then a variable to hold the state: var isEditingMode = false
3- Now in the viewDidLoad:
override func viewDidLoad() {
…
self.edit.action = #selector(self.toogleEditor(_:))
self.edit.title = "Edit"
self.setEditing(isEditingMode, animated: true)
…
}
I initialize the edit.action Selector to my custom function toogleEditor. I want to be able to change the title whenever an action occur.
4- Create an IBAction:
#IBAction func toogleEditor(sender: AnyObject) {
if isEditingMode
{
isEditingMode = false
self.edit.title = "Edit"
}
else
{
isEditingMode = true
self.edit.title = "Done"
}
self.setEditing(isEditingMode, animated: true)
}
This function is triggered each time the user click the UIBarItemButton.
The only thing to do is use the setEditing(…) to change the behaviour of the UITableViewController.
If anyone wondering how to do it from the storyboard:
Try to edit the "Title" attribute in "Attributes Inspector" as shown below:
from the docs
init(title title: String?,
style style: UIBarButtonItemStyle,
target target: AnyObject?,
action action: Selector)

Resources