Linking two (or more) UISegmented Controls - ios

I'm trying to get the answer from my first segmented control to then automatically select the answer to the second (and more) segmented controls, if the selected answer is No.
So Question1 segmented control (Yes, No, N/A), if No is selected, then make answer to Question2 (and Q3,Q4 etc) segmented control to N/A.
I can get it to work on Question1 alone, ie press No but changes to N/A on Question1, but I want it to leave Question1 as No, but then change Question2 etc to N/A.
I tried changing sender to Question2, but that doesn't work.
#IBOutlet weak var Queston2: UISegmentedControl! //Will need to add same for Q3 etc
#IBAction func Question1(_ sender: UISegmentedControl) {
if sender.selectedSegmentIndex == 0 {
//Do stuff
}
else if sender.selectedSegmentIndex == 1 {
sender.selectedSegmentIndex = 2 // Change button to 'N/A', works for this question, but i want it to change Question2, not Question1.
#IBAction func Question2(_ sender: UISegmentedControl) { // generally repeats as Question1 above.
I've found a few similar answers, but they seem far more complicated than I think I need.

This is basically what you could do:
class ArrayOfSegsViewController: UIViewController {
#IBOutlet var q1SegControl: UISegmentedControl!
#IBOutlet var q2SegControl: UISegmentedControl!
#IBOutlet var q3SegControl: UISegmentedControl!
#IBOutlet var q4SegControl: UISegmentedControl!
#IBOutlet var q5SegControl: UISegmentedControl!
#IBAction func q1SegControlChangeed(_ sender: UISegmentedControl) {
if sender.selectedSegmentIndex == 0 {
// do stuff
} else if sender.selectedSegmentIndex == 1 {
// set all others to "N/A"
[q2SegControl, q3SegControl, q4SegControl, q5SegControl].forEach {
$0.selectedSegmentIndex = 2
}
} else {
// do stuff
}
}
}
In the == 1 block, you create an array of the other segmented controls and loop through, saying "for each control, set the selected segment index to 2."
Side notes: use
lowerCaseFirstChar
for variables and function names. Use
UpperCaseFirstChar
for classes / structs / enums / etc.

Related

How to move function with button sender to a separate file?

I have a difficulty in a simple task. I googled this topic, but other examples are complicated by additional syntax that I don't understand yet. Can you help me to solve it or give link if there is already was similar topic.
I need to move the function responsible for selecting the button to a separate file, because if the number of buttons increases, it will turn into a large sheet. So made a function in separate swiftfile, but naturally the new file does not know about any buttons in viewController and can't find it in scope. Also If i’m not mistaken i need give Bool and return String.
How can I transfer a function with button sender to a separate file so that it returns non-optional text value back in the ViewController?
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var Label1: UILabel!
#IBOutlet weak var BTC: UIButton!
#IBOutlet weak var ETC: UIButton!
#IBOutlet weak var LTC: UIButton!
var choice = "Choose coin"
var coinType = getCoinType()
#IBAction func optionSelected(_ sender: UIButton) {
BTC.isSelected = false
ETC.isSelected = false
LTC.isSelected = false
sender.isSelected = true
if BTC.isSelected == true{
ETC.isSelected = false
LTC.isSelected = false
choice = "BTC"
Label1.text = choice
}else if ETC.isSelected == true{
BTC.isSelected = false
LTC.isSelected = false
choice = "ETC"
Label1.text = choice
}else if LTC.isSelected == true{
ETC.isSelected = false
LTC.isSelected = false
choice = "LTC"
Label1.text = choice
}
}
}
new file, i can't understand how to get sender from buttons here
import Foundation
func getCoinType() -> String{
var choice: String
// my if else function
return choice
}
P.S. In general, is it possible to make it easier, without using UIPickerView?
On how to add functions in different swift file you have a few options. This a simple one: Create a new swift file and name it ViewController+Extenstions.swift (you can use any name though). Then add an extension to your ViewController class and add your function like this:
extension ViewController {
func getCoinType() -> String {
var choice: String
// my if else function
return choice
}
}
You can add as many functions as you need and in different files (all extensions to ViewController but of course with different file names).
PS, in your optionSelected function, you are setting:
BTC.isSelected = false
ETC.isSelected = false
LTC.isSelected = false
I don't know what you're trying to achieve, but by doing so, those if-else will never be executed since your are setting the three buttons as not-selected!
UPDATE:
For your button selection problem, you can do this:
1- Add tags to your buttons
You can do it inside viewDidLoad method. This way you can differentiate between different buttons.
override func viewDidLoad() {
super.viewDidLoad()
BTC.tag = 0
ETC.tag = 1
LTC.tag = 2
}
2- Connect the action of buttons
Although your three buttons can have three different handler functions, it's easier to connect all of them to a single handler. However, we can know which button is tapped based on the value of the tag we assign in the previous step. So, connect all buttons to optionSelected(_ sender: UIButton) through Storyboard (because you have used Storyboard.
3- Rewrite getCoinType function
func getCoinType(tag: Int) -> String? {
var choice: String?
switch tag {
case 0:
choice = "BTC"
case 1:
choice = "ETC"
case 2:
choice = "LTC"
default:
choice = nil
}
return choice
}
Buttons' handler
Now when a button is tapped we call getCoinType function with that button's tag as input argument. It will return the string and we assign it to the Label1:
#IBAction func optionSelected(_ sender: UIButton) {
let choice = getCoinType(tag: sender.tag)
Label1.text = choice
}
And you're done!

UISegmentedControl.noSegment stopped working with Xcode 11, iOS 13 [duplicate]

This question already has answers here:
Specifying UISegmentedControlNoSegment to UISegmentedControl's selectedSegmentIndex has no Effect on iOS 13
(3 answers)
Closed 3 years ago.
I've had two segmented controls stacked on top of each other, each with two options, so there's a 2x2 grid of filtering options for a search field. This has worked fine, but I just updated to Xcode 11 and UISegmentedControl.noSegment has stopped working when I try to update it in response to user selection. However, it works when I set the initial value to .noSegment in the property observer. isMomentary is set to false. The outlets are all set up correctly. Is there some update to UISegmentedControl behavior I'm missing, or is this a bug?
New, incorrect behavior shown here.
Current code that was working before, and stopped working after update:
#IBOutlet private weak var segmentedControlOne: UISegmentedControl!
#IBOutlet private weak var segmentedControlTwo: UISegmentedControl! {
didSet {
// Start with no segment selected on this control. This works!
segmentedControlTwo.selectedSegmentIndex = -1
}
}
#IBAction private func oneIndexChanged(_ sender: UISegmentedControl) {
//Turn off selection on second control while first is selected
segmentedControlTwo.selectedSegmentIndex = UISegmentedControl.noSegment
let i = sender.selectedSegmentIndex
if i == 0 {
searchType = .users
} else {
searchType = .contributors
}
}
#IBAction private func twoIndexChanged(_ sender: UISegmentedControl) {
//Turn off selection on first control while second is selected
segmentedControlOne.selectedSegmentIndex = UISegmentedControl.noSegment
let i = sender.selectedSegmentIndex
if i == 0 {
searchType = .articles
} else {
searchType = .categories
}
}
Thanks for asking this question. I ran into the same issue, so was great to get some confirmation it wasn't just something I was missing.
While Apple hopefully fixes this bug soon, I implemented the following workaround by recreating the segments. This code sample is based on a UISegmentedControl with images as segments, you could of course implement the same approach for title strings:
public func resetSegmentedControl(_ control: UISegmentedControl) {
if #available(iOS 13, *) {
// workaround: recreate the segments
let numSegments = control.numberOfSegments
let segmentImages = (0..<numSegments).compactMap { control.imageForSegment(at: $0) }
control.removeAllSegments()
for (index, image) in segmentImages.enumerated() {
control.insertSegment(with: image, at: index, animated: false)
}
} else {
// for earlier versions of iOS, just reset the selectedSegmentIndex
control.selectedSegmentIndex = UISegmentedControl.noSegment
}
}
There's a slight flicker when removing and re-inserting the segments, but for me that's preferable to the broken state.
EDIT
As pointed out by #matt in the comment below, all that's needed is a call to setNeedsLayout,i.e.:
control.selectSegmentIndex = .noSegment
control.setNeedsLayout()

Setting isHidden to false on x amount of elements

I am trying to unhide n number of elements depending on the users input into a text field.
So the user enters a number between 1 - 5 in the text field then clicks submit which calls createSplit. As you can see, it unhides a view and then I want it to loop x (x being the number the user inputs) amount of times to unhide day(i)View textfield
#IBAction func createSplit(_ sender: Any)
{
noOfExerciseView.isHidden = false
let noOfDays: Int = Int(numberOfDays.text!)!
for i in 1...noOfDays
{
day\(i)View.isHidden = false
}
}
I have a working solution but it's not the most efficient so I hope someone can help doing this an efficient way.
#IBAction func createSplit(_ sender: Any)
{
noOfExerciseView.isHidden = false
let noOfDays: Int = Int(numberOfDays.text!)!
for i in 1...noOfDays
{
if (i==1)
{
day1View.isHidden = false
} else if (i==2)
{
day2View.isHidden = false
} else if (i==3)
{
day3View.isHidden = false
} else if (i==4)
{
day4View.isHidden = false
} else if (i==5)
{
day5View.isHidden = false
}
}
}
String interpolation cannot be used to set the name of a variable:
day\(i)View.isHidden // does not work
Your best bet is to use an outlet collection to define all your day views.
Instead of this:
#IBOutlet var day1View: UITextField!
#IBOutlet var day2View: UITextField!
#IBOutlet var day3View: UITextField!
//...
Do this:
#IBOutlet var dayViews: [UITextField]!
Then you can write your loop like this:
for i in 0...noOfDays-1
{
dayViews[i].isHidden = false
}
Note that to do this, you'll need to delete the existing outlets and reconnect them.
If you're using a storyboard, then when you Control-drag from your first text field to your class file, select Outlet Collection for the Connection type and name it dayViews. To add the remaining text fields to the collection, just Control-drag from each one to the dayViews var in your class file.

How to use one IBAction for multiple buttons in Swift?

I have multiple buttons each one with the ability to switch the language of the app. Instead of having to create multiple IBActions for each button is there a way to have them all connected to one IBAction and change the language based on the button pressed? I'm thinking a switch statement would be good to use in this situation but not exactly sure how to set it up.
In Interface Builder, select the Attributes Inspector and set the Tag for each button with a unique number, then you can do something like this:
#IBAction changeLanguage(sender: AnyObject) {
guard let button = sender as? UIButton else {
return
}
switch button.tag {
case 1:
// Change to English
case 2:
// Change to Spanish
case 3:
// Change to French, etc
default:
print("Unknown language")
return
}
}
To connect the action to multiple buttons: in Interface Builder, right-click ViewController in the view hierarchy, then left-click to drag the action connection to each button.
Yes, a switch statement is the way to go here. For a UIButton, you link it to a selector that is called when the user interacts with the button, generally the TouchUpInside event. The addTarget method, and valid selector signatures (apple.com) Of these, you want to use a method in the format #IBAction func doSomething(sender: UIButton) or #IBAction func doSomething(sender: UIButton, forEvent event: UIEvent), so that a reference to the button that triggered the event is passed to the selector.
In your ViewController code, you'll have references to your UIButtons (possibly in a storyboard, or created manually.) Let's say you have
#IBOutlet weak var frenchButton: UIButton!
#IBOutlet weak var spanishButton: UIButton!
#IBOutlet weak var englishButton: UIButton!
You would connect all of them to the same method, and branch the logic based on which one was the sender. e.g.:
#IBAction func changeLanguage(sender: UIButton) {
switch sender {
case frenchButton:
// Change Language to French
print ("C'est si bon")
case spanishButton:
// or Spanish
print ("Muy Bueno")
case englishButton:
// or English
print ("It's pretty cool")
default:
break
}
}
Note: Case statements in Swift must be exhaustive, so you have to include a default case, even though it should never be called.
Do not set tag if you have reference to the button.
You can just compare the reference instead of tags. This way, you won't introduce a new bug, because unlike a tag that you type yourself, reference is created by compiler automatically.
#IBOutlet weak var firstButton: UIButton!
#IBOutlet weak var secondButton: UIButton!
#IBOutlet weak var thirdButton: UIButton!
#IBAction changeLanguage(sender: UIButton) {
if sender == firstButton {
} else if sender == secondButton {
} else if sender == thirdButton {
}
}

Creating a word game

I'm creating a word game using UIKit, and I want to represent the entire alphabet for the user in order to solve the puzzle, here is my code:
var emptyPos = [0]
#IBOutlet var pos1: UILabel!
#IBOutlet var pos2: UILabel!
#IBOutlet var pos3: UILabel!
#IBOutlet var pos4: UILabel!
#IBAction func btnA(sender: UIButton) {
letters(sender)
}
#IBAction func btnB(sender: UIButton) {
letters(sender)
}
#IBAction func btnC(sender: UIButton) {
letters(sender)
}
#IBAction func btnD(sender: UIButton) {
letters(sender)
}
func moveLetter (pos: UILabel, btn: UIButton) {
UIView.animateWithDuration(0.5, animations: { () -> Void in
btn.center = pos.center
})
}
func letters (btn: UIButton) {
switch emptyPos.count {
case 1:
moveLetter(pos1, btn: btn)
emptyPos.append(0)
println(emptyPos)
case 2:
moveLetter(pos2, btn: btn)
emptyPos.append(0)
println(emptyPos)
case 3:
moveLetter(pos3, btn: btn)
emptyPos.append(0)
println(emptyPos)
case 4:
moveLetter(pos4, btn: btn)
emptyPos.append(0)
println(emptyPos)
default:
println("Error")
}
}
The idea is the user has to click on a letter after letter to move them towards the empty labels and figure out the right word, and as you can see I went with making each letter a button and each empty space a label, but was wondering if there is a better way than creating 26 buttons for each letter. Linking all the buttons to a single function will not work because then I will have to rely on sender.tag which I cannot pass to my function in order to move the letter. So should I continue with what I'm doing or is there some better way to do this ?
if you make a custom UIButton you can add extra properties to the button, then change the sender of the #IBAction to your custom class, then just pass the button to your moveLetter and it can know what to do based on the information supplied by the button. then they can all share the same button press function
then if your buttons subclass has a #property string called ButtonLetter, you can define what its value is right in the storyboard instead of manually doing it in code for all of the buttons by giving it a runtime attribute like in the screen shot below
or you could be lazy and just get the text of the button and read what letter it is, but i would say this is a more proper way of going about it, cause this can apply to any type of button where maybe the text on the button isnt actually the value you want to use to do some computation when the button is pressed, but in your case it just so happens to be that way.

Resources