titleLabel.text vs currentTitle in Swift - swift

I am trying to make a simple calculator with Swift. I want to get the "text" on the buttons I created. The instructor in the tutorial is using a property:
#IBAction func appendDigit(sender: UIButton) {
let digit = sender.currentTitle
}
The question is, if I did this:
#IBAction func appendDigit(sender: UIButton) {
let digit = sender.titleLabel.text
}
What's the difference? Will they yield the same results?
If so, how does one know when to use which?

titleLabel.text is mainly using to configure the text of the button(for each state)
currentTitle is read only. This is mainly using to get the title that is currently displayed.
You can't set this property because this set automatically whenever the button state changes. You can use currentTitle to get the title string associated with the button instead of using titleLabel.text because currentTitle property is set automatically whenever the button state changes.

Both .currentTitle and titleLabel.text return same value,
but .currentTitle is read-only, you can't modify his value (and it's only available in iOS 8+). .titleLabel is available since iOS 3+ and you can modify text, font, etc.

For sender.currentTitle:
The current title that is displayed on the button. (read-only)
Declaration
var currentTitle: String? { get }
Discussion
The value for this property is set automatically whenever the button
state changes. For states that do not have a custom title string
associated with them, this method returns the title that is currently
displayed, which is typically the one associated with the
UIControlStateNormal state. The value may be nil.
For sender.titleLabel.text:
A view that displays the value of the currentTitle property for a
button. (read-only)
Declaration
var titleLabel: UILabel? { get }
Discussion
Although this property is read-only, its own properties are
read/write. Use these properties primarily to configure the text of
the button. For example:
let button = UIButton.buttonWithType(.System) as UIButton
button.titleLabel.font = UIFont.systemFontOfSize(12)
button.titleLabel.lineBreakMode = .ByTruncatingTail
Do not use the label object to set the text color or the shadow color.
Instead, use the setTitleColor:forState: and
setTitleShadowColor:forState: methods of this class to make those
changes.
The titleLabel property returns a value even if the button has not
been displayed yet. The value of the property is nil for system
buttons.
From Apple Documentation.
In simple word:
sender.titleLabel?.text = "NewTitle" //this will work
sender.currentTitle = "newTitle" //this will give you an error because it is read only property.
And:
let digit = sender.currentTitle
let digit = sender.titleLabel!.text
This both will work because you are reading button's property and assigning it to digit instance.

Related

What does UISearchBar.value for key do in swift?

When using a UITableView and UISearchBar in swift, I was trying to find a way to keep the cancel button on the search bar enabled when the user searches something then scrolls in the table view, so they don't have to click twice to cancel the search. The default behaviour for resigning the first responder (or ending editing on the search bar) will gray out the cancel button, but I wanted to keep it enabled, so on Stackoverflow I found how to do this, but I can't find an answer online as to what searchBar.value(forKey: "cancelButton") does in the code below. Obviously it's somehow creating a reference to the cancel button on the search bar, but I don't understand what .value does (as I'm new to swift), and where the "cancelButton" key is coming from. I have read the func value(forKey key: String) documentation, but I still didn't understand it. It would be great if someone could explain what this is doing.
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
searchBar.resignFirstResponder()
// If the user scrolls, keep the cancel button enabled
if let cancelButton = searchBar.value(forKey: "cancelButton") as? UIButton { // <-- This line
if searchBar.text != "" { cancelButton.isEnabled = true }
else { searchBar.showsCancelButton = false }
}
}
Thanks in advance.
UISearchBar is a subclass of
UIView -> UIResponder -> NSObject
And all NSObjects are conforming the NSKeyValueCoding Protocol Reference
valueForKey: is a KVC method. It works with ANY NSObject class and anything else that conforms to the above protocol. valueForKey: allows you to access a property using a string for its name. So for instance, if I have an Account class with a property number, I can do the following:
let myAccount = Account(number: 12)
myAccount.value(forKey: "number")
Since it is a runtime check, it can't be sure what the return type will be. So you have to cast it manually like:
let number = myAccount.value(forKey: "number") as? Int
I'm not going to explain the downcast and optionals here
So you can access any property of an object that conforms to NSKeyValueCoding just by knowing its method's exact name (that can be found easily by a simple reverse engineering).
Also, there is a similar method called performSelector that lets you execute any function of the object
⚠️ But be aware that Apple will reject your app if you touch a private variable or function of a system. (If they found out!)
⚠️ Also, be aware that any of these can be renamed without notice and your app will face undefined behaviors.
I was running into the same problem as you for searching through a list but I realized you can implement UITextfields instead...
Using didReturn to do textfield.resignFirstResponder() and when it resigns to take the value using textfield.value to search through a list.
In searchbar for iOS 12 or below, to access the elements you can use key value to access the elements. Like this function -
private func configureSearchBar() {
searchBar.barTintColor = Color.navBarColor
searchBar.makeRounded(cornerRadius: 5, borderWidth: 1, borderColor: Color.navBarColor)
searchBar.placeholder = searchBarPlaceholderText
searchBar.setImage(Images.search_white, for: .search, state: .highlighted)
searchBar.setImage(Images.green_check, for: .clear, state: .normal)
if let textField = searchBar.value(forKey: "searchField") as? UITextField {
textField.textColor = .white
textField.attributedPlaceholder = NSAttributedString(string: textField.placeholder ?? "", attributes: [NSAttributedString.Key.foregroundColor: UIColor.white])
let leftSideImage = textField.leftView as? UIImageView
leftSideImage?.tintColor = .white
}
if let cancelButton = searchBar.value(forKey: "cancelButton") as? UIButton {
cancelButton.setTitleColor(.white, for: .normal)
}
}
Here by using the keys we are accessing the elements of the searchbar.

how to hide button title but can still call it using sender.currentTitle?

I added a button in the main storyboard in Xcode. This button has an image as the background and its title "blueDoor" is shown on top of the background (see photo below).
There will be three buttons like this and they are linked to one IBAction. I would like to use sender.currentTitle to let the program know which button is clicked, but I don't want the text to show on the image.
How can I hide the text but still keep the title attribute so sender.currentTitle can be used? Or is there another way to do so?
A button with an image as the background:
You can use tag to do this.
Open storyboard > select your button > select Show the Attributes Inspector section in the right bar of the Xcode > scroll down and find Tag under the View section. And give different tags to your buttons.
Then your IBAction should be like this ->
#IBAction func didTapMyButtons(_ sender: UIButton) {
switch sender.tag:
case 0: // it is your first button's tag
// Do something with button which has tag `0`
case 1: // it is your second button's tag
// Do something with button which has tag `1`
case 2: // it is your third button's tag
// Do something with button which has tag `2`
default: break
}
Shortly: you can not do that. The currentTitle property is essentially a getter for button.titleLable.text. So is title(for: UIControl.State) setter (setTitle(String?, for: UIControl.State))
What docs say:
var currentTitle: String? { get }
Discussion
The value for this property is set automatically whenever
the button state changes. For states that do not have a custom title
string associated with them, this method returns the title that is
currently displayed, which is typically the one associated with the
normal state. The value may be nil.
This means whatever changes you bring to titleLabel.text, it is automatically reflected on a currentTitle property.
You can use tags as suggested or add a custom action handler for every button as you create them. E.g. for cancel and proceed buttons one will do it like so:
self.cancelButton.addTarget(self, action: #selector(self.cancelActionMethod), for: .touchUpInside)
self.proceedButton.addTarget(self, action: #selector(self.proceedActionMethod), for: .touchUpInside)
And then somewhere inside self:
#objc func cancelActionMethod() {
// some cancel specific actions
}
#objc func proceedActionMethod() {
// some cancel specific actions
}

Can I observe an optional value in swift? If not, how might I go about attempting to observe a change?

I'm trying to observe a change to the selection of an NSPopUpButton in Swift 4. In my view controller's viewDidLoad() I've set up the observation token to observe the selectedItem property of the NSPopUpButton
override func viewDidLoad() {
super.viewDidLoad()
observation = observe(\.myPopUpButton.selectedItem) {
objectToObserve, change in
if change.kind == NSKeyValueObservedChange.Kind.setting {
// code to execute goes here
}
}
I set a breakpoint on the line where observation is set to determine that the token is being configured with the correct key path. I also set a break inside the closure to see when it is executed. When I change the selection of the NSPopUpButton, the closure does not execute.
selectedItem is of type, NSMenuItem?, so my suspicion is that I can't set an observation on an optional property. But I can't find anything in Apple's documentation that states whether or not that is the case and I'm not sure how I would go about verifying it for myself.
So I have sort of a primary question along w/ some followups:
Can I observe an optional property in Swift 4.1?
If so, how can I troubleshoot this, what am I doing wrong?
If not, how can I go about trying to monitor the state of the NSPopUpButton?
Troubleshoots that I've already tried...
added #objc dynamic to the my myPopUpButton declaration
Many properties of many AppKit objects are not KVO-compliant. Unless the documentation specifically says the property is compliant, you should assume it's not compliant. NSPopUpButton's selectedItem property is non-compliant.
The easiest way to be notified that the pop-up button's selected item changed is to set the button's target and action:
override func viewDidLoad() {
super.viewDidLoad()
myPopUpButton.target = self
myPopUpButton.action = #selector(popUpButtonDidFire(_:))
}
#IBAction private func popUpButtonDidFire(_ sender: Any) {
// code to execute goes here
}
Note that if you're creating the pop-up button in a storyboard or xib, you can wire it to the popUpButtonDidFire method by control-dragging from the pop-up button to the view controller.
As mentioned in the comments in macOS Cocoa Bindings and Swift's property observers are a very powerful way to observe values, even in prior Swift versions. An outlet is not needed.
Create a property and use the didSet observer
#objc dynamic var selectedObject : MyObject? {
didSet {
}
}
In Interface Builder in Bindings Inspector bind Selected Object to the target controller Model Key Path > selectedObject.
MyObject is the type of the represented object of the menu item. If nothing is selected selectedObject is nil. You can bind also Selected Index, Selected Tag or Selected Value (but not simultaneously).

Changing the value of a variable in another a class using a UIButton Swift 2

I have a View Controller (called BackgroundViewController) which has a few buttons, each of them set the color of the background of a different view, my main view. (just called ViewController, yes I started this project about a month ago, before I knew that I should name it something better). For that I set a class, SoundboardBrain, which I intend to use to hold a lot of the app's logic. Here's the class so far:
var backgroundName = String()
init(){
backgroundName = "Image"}
func changeBackgroundName(background: String){
backgroundName = background}
Now, BackgroundViewController is kind of like a settings pane, where the user could select one of the options and a bullet point appears by the one that he checked. Here's one of the of the buttons:
#IBAction func whiteButton(sender: AnyObject){
whiteBullet.hidden = false
imageBullet.hidden = true
}
//Here I call the changeBackground function I defined in SoundboardBrain
SoundboardBrain.changeBackgroundName("White")
//I then print the result of that and I still get "Image" NO MATTER WHAT!
So all I want to know is how to change a variable initialized in a class with a UIButton or another object of a ViewController.
You should keep the instance of SoundBrain somewhere in variable, or use a singleton. You could be initializing a new SoundBrain instance later.
Singleton is better for main app logic. Example:
class SoundboardBrain {
static let shared = SoundboardBrain()
var backgroundName = "Image"
func changeBackgroundName(background: String) {
backgroundName = background
}
}
SoundboardBrain.shared.backgroundName
// now the property is "Image"
// in UIButton
SoundboardBrain.shared.changeBackgroundName("something")
SoundboardBrain.shared.backgroundName
// now it's "something"
Example was made in Playground, but it doesn't matter.

How can I use didSet to change the text of a UITextField (IBOutlet)?

I'd like to set text value of UITextField (IBOutlet) in the DidSet of my model object that I pass.
Here the code:
let manageSettingViewController: ManageSettingViewController = self.storyboard?.instantiateViewControllerWithIdentifier("ManageSettingViewController") as ManageSettingViewController
self.navigationController?.pushViewControllerCustom(manageSettingViewController)
manageSettingViewController.setting = setting
And in the didSet of manageSettingViewController:
var setting: Setting? {
didSet
{
keyTextField.text = setting?.label
valueTextField.text = setting?.value
}
How can I set the text? Because in this case Xcode crash because "keyTextField is nil" :(
You're setting manageSettingViewController.setting right after instantiating manageSettingViewController -- at this point, it hasn't loaded its view from the nib/storyboard yet, so all of its IBOutlet variables (which presumably keyTextField and valueTextField are) are still nil. Those text fields are hooked up as of when ManageSettingViewController's viewDidLoad method is called.
You could change your didSet to check the optional outlets before setting them, or assign through optional chaining:
didSet {
keyTextField?.text = setting?.label
valueTextField?.text = setting?.value
}
This would avoid the crash, but it would also fail to change your text field's content. You'd have to also implement viewDidLoad for ManageSettingViewController to check its setting property and set its text fields accordingly.
Of course, that would duplicate the code from your didSet. That code might still be useful if you want to set setting from elsewhere and have the UI update automatically, but didSet won't help you for updating UI before the UI loads.