How can I connect two segment controls to each other? - swift

I'm trying to disable the bottom segment control if the user clicks Diesel. The problem is I can't connect two IBActions to each other. Such as:
#IBAction func didSelect(_ control: UISegmentedControl) {
switch control.selectedSegmentIndex
{
case 0:
isPetrol = true
isDiesel = false
case 1:
isPetrol = false
isDiesel = true
default:
print ("break")
}
}
#IBACtion func didSecondSelect (_ control: UISegmentedControl) {
//something here that when case1 is clicked disables it
}
}
How can I disable it if the top case 1 is clicked?

You are mixing up IBActions and IBOutlets.
You should create an IBOutlet that points to the second segmented control and change that from the first one's code.
#IBOutlet weak var secondSegmentedControl: UISegmentedControl!
#IBAction func didSelect(_ control: UISegmentedControl) {
[...]
secondSegmentedControl.isEnabled = control.selectedSegmentIndex == 0
}
For more information about working with IBOutlets, check out this question.

Related

Control (UIDatePicker) doesn't trigger change event when it starts out hidden

I'm new to iOS development, but I'm having an issue with one of the views that I'm working on. I have a UIDatePicker that can either be hidden or visible depending on the state of a UISwitch. It seems that the associated #IBAction does not trigger when the view starts out hidden. It does work when the date picker starts out visible, so the IBAction is working.
Here's a simplified version of my code:
import UIKit
class StatusEditorViewController: UIViewController {
#IBOutlet var expiryPicker: UIDatePicker!
#IBOutlet var enableExpirySwitch: UISwitch!
var editingObject: StoredStatus?
private var pickerIsVisible = false
private var expiresIn: TimeInterval?
override func viewDidLoad() {
// Set a default value
expiryPicker.countDownDuration = TimeInterval(3600)
// If this view got passed an object to edit, use that for expiresIn
if let status = editingObject {
if let expires = status.expiresIn.value {
expiresIn = TimeInterval(expires)
}
}
// Hide the picker and turn off the "enable expiry" switch if we don't
// have a value yet. We'll show the picker once the switch has been pressed
pickerIsVisible = expiresIn != nil
enableExpirySwitch.isOn = expiresIn != nil
updatePicker()
}
func updatePicker() {
expiryPicker?.isHidden = !pickerIsVisible
}
#IBAction func expiryDidUpdate(_ sender: UIDatePicker) {
expiresIn = sender.countDownDuration
print(expiresIn!)
}
#IBAction func expirySwitchDidUpdate(_ sender: UISwitch) {
pickerIsVisible = sender.isOn
updatePicker()
// If the user just turned on the switch, we want to make sure we store the
// initial value already, in case the user navigated away
if (sender.isOn && expiresIn == nil) {
expiresIn = expiryPicker.countDownDuration
}
}
}
I'm not sure what's going wrong. I tried manually attaching a target (e.g. self.expiryPicker.addTarget(self, action: #selector(setExpiryValue), for: .allEditingEvents))
once the view becomes available, but that didn't work either.
I hope someone can tell me what I'm doing wrong. I'm guessing there's something fundamental that I'm doing wrong, but so far no search on Google or SO has led me to the answer.
Thanks in advance
f.w.i.w, I'm running XCode 11.7, with Swift 5, with a deployment target of iOS 13.7

Why would NSWindowController return nil-value window property?

I'm using modal sheets (slide down from top) to get user input. I currently have 2 that I think are identical except for the UI, each a NIB + NSWindowController-subclass pair. One works as expected, binding input to an array controller and table view. When trying to use the other, the window property of the NSWindowController is nil.
This code works:
#IBAction func addItemButtonClicked(_ button: NSButton) {
let window = document?.windowForSheet
let windowController = NewItemSheetController()
windowController.typeChoices = newItemSheetTypeChoices
windowController.windowTitle = newItemSheetTitle
print(#function, windowController.window) // output below
window?.beginSheet(windowController.window!, completionHandler: { response in
// The sheet has finished. Did user click OK?
if response == NSApplication.ModalResponse.OK {
let structure = (self.newItemSheetController?.structure)!
self.document?.dataSource.structures.append(structure)
}
// All done with window controller.
self.newItemSheetController = nil
})
newItemSheetController = windowController
}
The output of the print statement: "addItemButtonClicked(_:) Optional()"
This code doesn't:
#IBAction func addItemButtonClicked(_ button: NSButton) {
let window = document?.windowForSheet
let windowController = NewRecurrenceItemSheetController()
windowController.windowTitle = newItemSheetTitle
print(#function, windowController.window)
window?.beginSheet(windowController.window!, completionHandler: { response in
// The sheet has finished. Did user click OK?
if response == NSApplication.ModalResponse.OK {
let recurrence = (self.newItemSheetController?.recurrence)!
self.document?.dataSource.recurrences.append(recurrence)
}
// All done with window controller.
self.newItemSheetController = nil
})
newItemSheetController = windowController
}
The output of the print statement: "addItemButtonClicked(_:) nil"
Classes NewItemSheetController and NewRecurrenceItemSheetController are subclasses of NSWindowController and differ only with NSNib.Name and properties related to differing UI. As far as I can see, the XIBs and Buttons are "wired" similarly. The XIBs use corresponding File's Owner. Window objects have default class.
#objcMembers
class NewItemSheetController: NSWindowController {
/// other properties here
dynamic var windowTitle: String = "Add New Item"
override var windowNibName: NSNib.Name? {
return NSNib.Name(stringLiteral: "NewItemSheetController")
}
override func windowDidLoad() {
super.windowDidLoad()
titleLabel.stringValue = windowTitle
}
// MARK: - Outlets
#IBOutlet weak var titleLabel: NSTextField!
#IBOutlet weak var typeChooser: NSPopUpButton!
// MARK: - Actions
#IBAction func okayButtonClicked(_ sender: NSButton) {
window?.endEditing(for: nil)
dismiss(with: NSApplication.ModalResponse.OK)
}
#IBAction func cancelButtonClicked(_ sender: NSButton) {
dismiss(with: NSApplication.ModalResponse.cancel)
}
func dismiss(with response: NSApplication.ModalResponse) {
window?.sheetParent?.endSheet(window!, returnCode: response)
}
}
Why does one return instantiate a windowController object with a nil-valued window property?
In Interface Builder, the XIB Window needed to be attached to File's Owner with a Window outlet and delegate. Thanks #Willeke.

Using a UISegmentedControl like a UISwitch

Is it possible to use a UISegmentedControl with 3 segments as if it was a three-way UISwitch? I tried to use one as a currency selector in the settings section of my app with no luck, it keeps reseting to the first segment when I switch views and that creates a big mess.
I proceeded like that:
IBAction func currencySelection(_ sender: Any) {
switch segmentedControl.selectedSegmentIndex {
case 0:
WalletViewController.currencyUSD = true
WalletViewController.currencyEUR = false
WalletViewController.currencyGBP = false
MainViewController().refreshPrices()
print(0)
case 1:
WalletViewController.currencyUSD = false
WalletViewController.currencyEUR = true
WalletViewController.currencyGBP = false
MainViewController().refreshPrices()
print(1)
case 2:
WalletViewController.currencyUSD = false
WalletViewController.currencyEUR = false
WalletViewController.currencyGBP = true
MainViewController().refreshPrices()
print(2)
default:
break
}
}
The UISegmentedControl is implemented in the
SettingsViewController of the app to choose between currencies to
display in the MainViewController.
(Taken from a comment in #pacification's answer.)
This was the missing piece I was looking for. It provides a lot of context.
TL;DR;
Yes, you can use a three segment UISegmentedControl as a three-way switch. The only real requirement is that you can have only one value or state selected.
But I wasn't grasping why your code referred to two view controllers and some of switching views resulting in resetting the segment. One very good way to do what you want is to:
Have MainViewController present SettingsViewController. Presenting it modally means the user is only doing one thing at a time. When they are making setting changes, you do not want them adding new currency values.
Create a delegate protocol in SettingsViewController and make MainViewController conform to it. This tightly-couples changes made to the settings to the view controller interested in what those changes are.
Here's a template for what I'm talking about:
SettingsViewController:
protocol SettingsVCDelegate {
func currencyChanged(sender: SettingsViewController)
}
class SettingsViewController : UIViewController {
var delegate:SettingsVCDelegate! = nil
var currency:Int = 0
#IBAction func valueChanged(_ sender: UISegmentedControl) {
currency = sender.selectSegmentIndex
delegate.currencyChanged(sender:self)
}
}
MainViewController:
class MainViewController: UIViewController, SettingsVCDelegate {
var currency:Int = 0
let settingsVC = SettingsViewController()
override func viewDidLoad() {
super.viewDidLoad()
settingsVC.delegate = self
}
func presentSettings() {
present(settingsVC, animated: true, completion: nil)
}
func currencyChanged(sender:SettingsViewController) {
currency = sender.currency
}
}
You can also create an enum of type Int to make your code more readable, naming each value as currencyUSD, currencyEUR, and currencyGBP. I'll leave that to you as a learning exercise.
it keeps reseting to the first segment when I switch views
yes, it is. to avoid this situation you should set the correct switch value to the segmentedControl.selectedSegmentIndex every time when you load your view with UISegmentedControl.
UPD
Ok, the behavior of MainViewController can be similar to this:
final class MainViewController: UIViewController {
private var savedValue = 0
override func viewDidLoad() {
super.viewDidLoad()
}
func openSettingsController() {
let viewController = SettingsController.instantiate() // simplify code a bit. use the full controller initialization
viewController.configure(value: savedValue, onValueChanged: { [unowned self] value in
self.savedValue = value
})
navigationController?.pushViewController(viewController, animated: true)
}
}
And the SettingsViewController:
final class SettingsViewController: UIViewController {
#IBOutlet weak var segmentedControl: UISegmentedControl!
private var value: Int = 0
var onValueChanged: ((Int) -> Void)?
func configure(value: Int, onValueChanged: #escaping ((Int) -> Void)) {
self.value = value
self.onValueChanged = onValueChanged
}
override func viewDidLoad() {
super.viewDidLoad()
segmentedControl.selectedSegmentIndex = value
}
#IBAction func valueChanged(_ sender: UISegmentedControl) {
onValueChanged?(sender.selectedSegmentIndex)
}
}
The main idea that you should keep your selected value if you moving from SettingsViewController. For this thing you can create closure
var onValueChanged: ((Int) -> Void)?
that pass back to MainViewController the selected UISegmentedControl value. And in future when you will open the SettingsViewController again you just configure() this value and set it to UI.

Xcode v7.2 - New Radio Buttons: How to find selected

I am attempting to detect which Radio Button is currently selected using Xcode 7's new Radio Button template (NSButton).
I have created a simple action that will print to log the title of the sender when a radio button is selected. This does work:
#IBAction func radioButtonClicked(sender: AnyObject)
{
print(sender.selectedCell()!.title);
}
But what I am really looking for is the ability to know which radio button is selected elsewhere in my codes (specifically on an IBAction for a button click). I have tried:
#IBAction func uploadButtonPressed(sender: AnyObject)
{
print (radioButton.selectedCell()!.title);
}
This does compile and execute, the problem is it always gives me the title of the first radio button not the one that is actually selected.
Any ideas?
The "closest" I can get (which is not very clean, but works "kinda") is to see if radioButton.cell?state = 1. This tells me that the first radio button is selected. But this is a very poor way to code this and only allows for 2 radio button options.
if (radioButton.cell?.state == 1)
{
print ("Radio 1 Selected");
}
else
{
print ("Radio 2 Selected");
}
Use the identifier or tag property in Interface Builder to distinguish between radio buttons like:
#IBAction func selectOption(sender: AnyObject) {
let id:String = sender.identifier!!
case "first identifier":
// do some stuff
// ...
default:
// do default stuff
}
Another option would be a switch-case statement and defining outlets for each radio button like:
#IBOutlet weak var firstOption: NSButton!
#IBOutlet weak var secondOption: NSButton!
#IBOutlet weak var thirdOption: NSButton!
#IBAction func selectOption(sender: AnyObject) {
switch sender as! NSButton {
case firstOption:
print("firstOption")
case secondOption:
print("secondOption")
case thirdOption:
print("thirdOption")
default:
print("won't be called when exhaustive but must always be implemented")
}
}
I ended up finding a method for this.
First I setup a variable to store which item was checked (and pre-set the value to the value I want for the first radio button)
var databaseUploadMethod = "trickle-add";
Then I compare to see the title value of the selected item:
#IBAction func radioButtonClicked(sender: AnyObject)
{
if ((sender.selectedCell()!.title) == "Trickle Add")
{
databaseUploadMethod = "trickle-add";
}
else if ((sender.selectedCell()!.title) == "Bulk Add All Now")
{
databaseUploadMethod = "bulk-add";
}
}
For reference: I have two radio buttons, the first has a title of "Trickle Add" and the second is "Bulk Add All Now". Those are the values I am comparing in my if statement.

Get button pressed id on Swift via sender

So I have a storyboard with 3 buttons I want to just create 1 action for all those 3 buttons and decide what to do based on their label/id...
Is there a way to get some kind of identifier for each button?
By the way they are images, so they don't have a title.
#IBAction func mainButton(sender: UIButton) {
println(sender)
}
You can set a tag in the storyboard for each of the buttons. Then you can identify them this way:
#IBAction func mainButton(sender: UIButton) {
println(sender.tag)
}
EDIT: For more readability you can define an enum with values that correspond to the selected tag. So if you set tags like 0, 1, 2 for your buttons, above your class declaration you can do something like this:
enum SelectedButtonTag: Int {
case First
case Second
case Third
}
And then instead of handling hardcoded values you will have:
#IBAction func mainButton(sender: UIButton) {
switch sender.tag {
case SelectedButtonTag.First.rawValue:
println("do something when first button is tapped")
case SelectedButtonTag.Second.rawValue:
println("do something when second button is tapped")
case SelectedButtonTag.Third.rawValue:
println("do something when third button is tapped")
default:
println("default")
}
}
If you want to create 3 buttons with single method then you can do this by following code...Try this
Swift 3
Example :-
override func viewDidLoad()
{
super.viewDidLoad()
Button1.tag=1
Button1.addTarget(self,action:#selector(buttonClicked),
for:.touchUpInside)
Button2.tag=2
Button2.addTarget(self,action:#selector(buttonClicked),
for:.touchUpInside)
Button3.tag=3
Button3.addTarget(self,action:#selector(buttonClicked),
for:.touchUpInside)
}
func buttonClicked(sender:UIButton)
{
switch sender.tag
{
case 1: print("1") //when Button1 is clicked...
break
case 2: print("2") //when Button2 is clicked...
break
case 3: print("3") //when Button3 is clicked...
break
default: print("Other...")
}
}
You can create an outlet for your buttons and then implement:
#IBAction func mainButton(sender: UIButton) {
switch sender {
case yourbuttonname:
// do something
case anotherbuttonname:
// do something else
default: println(sender)
}
}
Swift 4 - 5.1
#IBAction func buttonPressed(_ sender: UIButton) {
if sender.tag == 1 {
print("Button 1 is pressed")
}
}
You have to set tag value to what you need and access it with
sender.tag
Assuming you gave them all proper names as #IBOutlets:
#IBOutlet var weak buttonOne: UIButton!
#IBOutlet var weak buttonTwo: UIButton!
#IBOutlet var weak buttonThree: UIButton!
You can use the following to determine which is which
#IBAction func didPressButton(sender: AnyObject){
// no harm in doing some sort of checking on the sender
if(sender.isKindOfClass(UIButton)){
switch(sender){
case buttonOne:
//buttonOne action
break
case buttonTwo:
//buttonTwo action
break
case buttonThree:
//buttonThree action
break
default:
break
}
}
Swift 3 Code:
In xcode Please set tag for each button first to work following code.
#IBAction func threeButtonsAction(_ sender: UIButton) {
switch sender.tag {
case 1:
print("do something when first button is tapped")
break
case 2:
print("do something when second button is tapped")
break
case 3:
print("do something when third button is tapped")
break
default:
break
}
}
You can do like this, just you have to give tag to all the buttons and do like this:
#IBAction func mainButton(sender: AnyObject)
{
switch sender.tag {
case 1:
println("do something when first button is tapped")
case 2:
println("do something when second button is tapped")
case 3:
println("do something when third button is tapped")
default:
println("default")
}
}
Use the outlets instead, tags clutter the code and make the readability way worse. Think about the poor developer that reads the code next and sees if sender.tag = 381 { // do some magic }, it just won't make any sense.
My example:
class PhoneNumberCell: UITableViewCell {
#IBOutlet weak var callButton: UIButton!
#IBOutlet weak var messageButton: UIButton!
#IBAction func didSelectAction(_ sender: UIButton) {
if sender == callButton {
debugPrint("Call person")
} else if sender == messageButton {
debugPrint("Message person")
}
}
[...]
}
You could also do this in a nice switch as well, which would make it even better.
Tested on Swift 5.1
Swift 4
add tag on button
let button = UIButton()
button.tag = 10
click event
#IBAction func mainButton(sender: UIButton) {
switch sender.tag {
case 10:
print("10")
case 11:
print("11")
default:
print("yes")
}
}
In this case you can use NSObject extension Accessibility Element UIAccessibility.
I have used accessibilityLabel and accessibilityIdentifier both are success in call and condition checking.
First
You can set a Accessibility Label or Identifier in the storyboard for each of the buttons in Identity inspector. Accessibility should be enabled.
To check/Identify button by
#IBAction func selectionPicker(_ sender: UIButton){
if sender.accessibilityLabel == "childType"{ //Check by accessibilityLabel
print("Child Type")
}
if sender.accessibilityIdentifier == "roomType"{ //Check by accessibilityIdentifier
print("Room Type")
}
performSegue(withIdentifier: "selectionViewSegue", sender:sender)
}
On Swift 3.2 and 4.0 with Xcode 9.0
Given the case you labeled your buttons "1", "2", "3":
#IBAction func mainButton(sender: UIButton) {
switch sender.titleLabel?.text {
case "1":
print("do something when first button is tapped")
case "2":
print("do something when second button is tapped")
case "3":
print("do something when third button is tapped")
default:
() // empty statement or "do nothing"
}
}
Swift 5.5
I have utility methods in other classes that do not have an instance of my ViewController, so I don't compare the sent objects to what is defined in the ViewController's IBOutlets.
I don't use tags if I can use a plain language identifier on my UI objects. I'd rather have plain language identifiers than numbers to identify my objects because it is easier for me. Just another way of doing it.
If I need to use a utility method, I set it up with a sender parameter so I can send the button and then figure out which button was clicked based on the assigned identity of the button within Storyboard.
For example:
class Utility {
func doSomething(sender: Any?) {
guard let button = sender as? NSButton else {
print("Unable to set button from sender.")
return
}
guard case buttonID = button.identifier?.rawValue else {
print("Unable to get button identifier.")
return
}
switch buttonID {
case: "firstButton":
_ = buttonID // Perform firstButton action
case: "secondButton":
_ = buttonID // Perform secondButton action
case: "thirdButton":
_ = buttonID // Perform thirdButton action
default:
// shouldn't get here - error?
}
}
}
In my ViewController I have the following buttons set up as IBOutlets and their identity is the same in Storyboard.
#IBOutlet weak var firstButton: NSButton?
#IBOutlet weak var secondButton: NSButton?
#IBOutlet weak var thirdButton: NSButton?
Then I have my IBActions:
#IBAction func firstButtonClicked(sender: Any?) {
utility.doSomething(sender: sender)
}
#IBAction func secondButtonClicked(sender: Any?) {
utility.doSomething(sender: sender)
}
#IBAction func thirdButtonClicked(sender: Any?) {
utility.doSomething(sender: sender)
}
In my case what i did, just like the answers above i used the tag to identify the specific button, what i added is that i added a UIButton extension that adds an id so that i can set a string id
i had three buttons with tags 0, 1 and 2
Then created the extension
extension UIButton {
var id: String {
let tag = self.tag
switch tag {
case 0:
return "breakfast"
case 1:
return "lunch"
case 2:
return "dinner"
default:
return "None"
}
}
}
When accessing a button in an IBAction i would just call:
sender.id
Select your first button and give it tag 0, and select second button and give it tag 1 and so on, in action check the tag bit and perform you functionalities on the basis of tag bit.:
switch sender as! NSObject {
case self.buttoneOne:
println("do something when first button is tapped")
case self.buttoneTwo:
println("do something when second button is tapped")
default:
println("default")
}