Swift refactoring assistance - swift

I have a meter laid out on a storyboard that is comprised of ten segments. There are an additional ten segments on top of the original segments that are colored to simulate a lighted segment - these are outlets. (See image below.) Currently I am using a switch statement to hide and unhide each outlet/segment based on a constantly varying input level. But, as you can see in the code below, it's not pretty. I keep reading that polymorphism is often the way to improve a switch statement, but I can't see how that would help here.
switch input {
case 0...9:
seg1.hidden = false
seg2.hidden = true
seg3.hidden = true
seg4.hidden = true
seg5.hidden = true
seg6.hidden = true
seg7.hidden = true
seg8.hidden = true
seg9.hidden = true
seg10.hidden = true
case 10...19:
seg1.hidden = false
seg2.hidden = false
seg3.hidden = true
seg4.hidden = true
seg5.hidden = true
seg6.hidden = true
seg7.hidden = true
seg8.hidden = true
seg9.hidden = true
seg10.hidden = true
...
and on and on for eight more levels.

//You can have these segments in an array like this-
let segments = [seg1, seg2, seg3, seg4, seg5,seg6, seg7, seg8, seg9, seg10]
// Function that will setup the segments based on input
func setUpSegmentsForInputValue(segments:[UIView], value:Int) {
for (index, segment) in segments.enumerate() {
segment.hidden = (value/10) != index
}
}
// Call the function with whatever input values
setUpSegmentsForInputValue(segments: segments, value: someValue)

You could add all of your outlets to an array. Map the input to an "index range" and then decide in a for loop if outlet is in "index range". Then set hidden property accordingly.
#IBOutlet weak var seg1 = UIView()
#IBOutlet weak var seg2 = UIView()
#IBOutlet weak var seg3 = UIView()
#IBOutlet weak var seg4 = UIView()
...
let segs = [seg1, seg2, seg3, seg4]
let input = 19
let range = 0 ... input / 10
for (index, seg) in segs.enumerate() {
if range ~= index {
seg.hidden = false
}else {
seg.hidden = true
}
}
Alternative (instead of the for loop):
#IBOutlet weak var seg1 = UIView()
#IBOutlet weak var seg2 = UIView()
#IBOutlet weak var seg3 = UIView()
#IBOutlet weak var seg4 = UIView()
...
let segs = [seg1, seg2, seg3, seg4]
let input = 19
let range = 0 ... input / 10
segs.enumerate().forEach { index, seg in
seg.hidden = !(range ~= index)
}
Hope this helps.

Related

How to hide and unhide a view with height in swift?

I am trying to hide and unhide a view. Its size should be 0 when it is hidden and about 200 when it is unhidden. I have two view controllers. When the first controller shows the view is hidden for the first time and its size is set to 0 and then it navigates to other controller and takes some values from the textfeilds and display them on a tableview in previous controller.
Now, I am able to hide the view for the first time with height 0 but when I take up the values the view is still hidden.
This is the code I have tried so far:
mainView.isHidden == true
mainView.heightAnchor.constraint(equalToConstant: CGFloat(0)).isActive = true
// when I get the values but this code doesn't work
mainView.isHidden == false
mainView.heightAnchor.constraint(equalToConstant: CGFloat(100)).isActive = true
Any help would be appreciated.
class viewController: UIViewController {
var height: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
height = mainView.heightAnch.constraint(equalToConstant: 0)
height.isActive = true
//handle change height
if mainView.isHidden == true {
height.constant = 0
}
else {
height.constant = 200
}
}
}
You have two options.
The first method set identifier to height constraint
set identifier:
then find and change with below code:
// first option
// find own constraint with indentifier
if let heightConstraint = self.myView.constraints.first(where: { item -> Bool in
return item.identifier == "heightIdentifier"
}) {
// set any constant to constraint
heightConstraint.constant = 200.0 // for hidden
heightConstraint.constant = 0.0 // for hide
// any work
}
second option: set IBOutlet to target constraint:
#IBOutlet weak var heightConstraint: NSLayoutConstraint!
then change direct and simple:
// second option
// change constant direct
self.heightConstraint.constant = 200.0 // for hidden
self.heightConstraint.constant = 0.0 // for hide
// any work
You keep both created constraints in your view controller and activate however you need accordingly, using isActive property of NSLayoutConstraint:
var hiddenHeightConstraint: NSLayoutConstraint?
var showingHeightConstraint: NSLayoutConstraint?
var isMainViewHidden: Bool = false {
didSet {
mainView.isHidden == isMainViewHidden
hiddenHeightConstraint?.isActive = isMainViewHidden
showingHeightConstraint?.isActive = !isMainViewHidden
// Don't forget to call layoutIfNeeded() when you messing with the constraints
view.layoutIfNeeded()
}
}
override func viewDidLoad() {
super.viewDidLoad()
hiddenHeightConstraint = mainView.heightAnchor.constraint(equalToConstant: CGFloat(0))
showingHeightConstraint = mainView.heightAnchor.constraint(equalToConstant: CGFloat(100))
isMainViewHidden = false
}

how to fix the wrong word wrap?

for a very long time I can’t solve a simple problem, namely: transferring a word to a new line and automatically reducing the label if the word does not fit. Tell me how to be in this situation.
#IBOutlet weak var wordLabel: UILabel!
#IBOutlet weak var transcriptionLabel: UILabel!
#IBOutlet weak var translationLabel: UILabel!
var index = 0
var word = ""
var transcription = ""
var translation = ""
override func viewDidLoad() {
super.viewDidLoad()
wordLabel.text = word
transcriptionLabel.text = ""
translationLabel.text = ""
wordLabel.adjustsFontSizeToFitWidth = true
wordLabel.minimumScaleFactor = 0.1
transcriptionLabel.adjustsFontSizeToFitWidth = true
transcriptionLabel.minimumScaleFactor = 0.1
translationLabel.adjustsFontSizeToFitWidth = true
translationLabel.minimumScaleFactor = 0.1
self.transcriptionLabel.alpha = 0.0
self.translationLabel.alpha = 0.0
}
Thanks humblePilgrim, for the suggested answer, it really helped. I leave the link to the source and attach the answer itself.
source
Swift 4.2
extension UILabel {
// Adjusts the font size to avoid long word to be wrapped
func fitToAvoidWordWrapping() {
guard adjustsFontSizeToFitWidth else {
return // Adjust font only if width fit is needed
}
guard let words = text?.components(separatedBy: " ") else {
return // Get array of words separate by spaces
}
// I will need to find the largest word and its width in points
var largestWord: NSString = ""
var largestWordWidth: CGFloat = 0
// Iterate over the words to find the largest one
for word in words {
// Get the width of the word given the actual font of the label
let wordWidth = word.size(withAttributes: [.font: font]).width
// check if this word is the largest one
if wordWidth > largestWordWidth {
largestWordWidth = wordWidth
largestWord = word as NSString
}
}
// Now that I have the largest word, reduce the label's font size until it fits
while largestWordWidth > bounds.width && font.pointSize > 1 {
// Reduce font and update largest word's width
font = font.withSize(font.pointSize - 1)
largestWordWidth = largestWord.size(withAttributes: [.font: font]).width
}
}
}
I thank user Dam for the answer.

How to make Swift 3 recognize a textfield name which is increasing?

I have 26 textfields which will provide different float values each, and I want to create a loop to make it easier & faster to add those values into a single variable.
My textfields are named a1, a2, ... a26 so the only thing that changes is the number after 'a'. How can I make swift to recognize the textfield name as I'm increasing the variable 'x'.
I was thinking something like this:
var x : Int = 1
var total : Float = 0;
var y : Float = 0;
while (x == 26) {
y = a/(x).floatValue;
total += y;
x+=1;
}
My idea is obviously not working 😕
Make an array containing all 26 text fields, and iterate over them:
let textfields = [a1, a2, ..., a26]
for textfield in textfields {
total += textfield.floatValue
}
I agree with #jtbandes' comment that it is best to start with arrays and loops, so you never need all 26 to be separate variables. If your textFields are #IBOutlets, you can use an outlet collection (which is just a single array to hold all of the textFields).
#IBOutlet var textfields: [NSTextField]!
Then you'd simply do:
for textfield in textfields {
total += textfield.floatValue
}
In the interest of showing what is possible, assuming the textfields are properties of your class (and your class derives from NSObject), you can use key-value coding to get the values:
for i in 1...26 {
if let tf = value(forKey: "a\(i)") as? NSTextField {
total += tf.floatValue
}
}
Full example for macOS:
import Cocoa
class ViewController: NSViewController {
var a1 = NSTextField()
var a2 = NSTextField()
var a3 = NSTextField()
func total() {
// give them some values for testing purposes
a1.stringValue = "1.2"
a2.stringValue = "2.3"
a3.stringValue = "3.14"
var total: Float = 0.0
for i in 1...3 {
if let tf = value(forKey: "a\(i)") as? NSTextField {
total += tf.floatValue
}
}
print(total)
}
}
ViewController().total() // 6.64
Full example for iOS:
import UIKit
class ViewController: UIViewController {
var a1 = UITextField()
var a2 = UITextField()
var a3 = UITextField()
func total() {
// give them some values for testing purposes
a1.text = "1.2"
a2.text = "2.3"
a3.text = "3.14"
var total: Float = 0.0
for i in 1...3 {
if let tf = value(forKey: "a\(i)") as? UITextField {
total += ((tf.text ?? "") as NSString).floatValue
}
}
print(total)
}
}
ViewController().total() // 6.64

When using < operator swift goes over specified number before exiting

I am currently learning swift/iOS programing. When I use a < operator and do something like: x < y, x will go past y before exiting. Is there a way around this so it stops as it hits y rather than going over?
Any and all help is greatly appreciated!
Thanks!
So when I press my AddBtnPressed button it adds firstNum and secondNum storing it in sumNum. Say firstNum = 995, secondNum = 15, when I hit my button it will hit 1010 instead of exiting and then if I hit my button again after that it will then exit after its already gone over.
Hope this makes sense!
The user inputs a number hits play which takes you to a second screen that says "press add to add" you press the Add button and it reveals an equation of 0 + (user input number) = x. and every time you press the Add button it adds the user number to the previous sum until it hits 999 then its supposed to take you back to the main screen.
import UIKit
class ViewController: UIViewController {
var firstNum = 0
var secondNum = 0
var sumNum = 0
var maxNum = 900
// First Screen
#IBOutlet weak var logo: UIImageView!
#IBOutlet weak var userNum: UITextField!
#IBOutlet weak var play: UIButton!
// Second Screen
#IBOutlet weak var pressAddToAdd: UILabel!
#IBOutlet weak var AddBtn: UIButton!
// Thrird Screen
#IBOutlet weak var ContSum: UILabel!
#IBOutlet weak var addUserNum: UILabel!
#IBOutlet weak var sum: UILabel!
#IBOutlet weak var mathSymbols: UILabel!
#IBAction func playBtnPressed(sender: UIButton) {
if userNum.text != nil && userNum.text != "" {
userNum.hidden = true
logo.hidden = true
play.hidden = true
pressAddToAdd.hidden = false
AddBtn.hidden = false
}
}
#IBAction func AddBtnPressed(sender: UIButton) {
if sumNum <= maxNum {
// Hide 2nd screen
pressAddToAdd.hidden = true
// Reveal 3rd screen
ContSum.hidden = false
addUserNum.hidden = false
sum.hidden = false
mathSymbols.hidden = false
// Variables
firstNum = sumNum
secondNum = Int (userNum.text!)!
sumNum = firstNum + secondNum
// Imput Variables into textFields
addUserNum.text = "\(secondNum)"
sum.text = "\(sumNum)"
ContSum.text = "\(sumNum - secondNum)"
} else {
// Restart
firstNum = 0
secondNum = 0
sumNum = 0
ContSum.hidden = true
addUserNum.hidden = true
sum.hidden = true
mathSymbols.hidden = true
AddBtn.hidden = true
userNum.hidden = false
logo.hidden = false
play.hidden = false
}
}
Evaluation your total outside of your conditional
sumNum += Int(userNum.text!)!
if sumNum <= 999 {
...
}
operator < is nothing but less than. less than can be true or false. expression x < y is evaluated as value of type Bool
// "do something like: x < y"
2 < 3 // true
"a" < "b" // true
var x = 0
var y = 1
x++ < y // true
print(x, y) // 1 1
--x < y // true
print(x, y) // 0 1

Check text field Live

I have found this answer How to check text field input at real time?
This is what I am looking for. However I am having trouble actually implementing this code. Also my current geographical location makes googling almost impossible.
I want to be able to change the background color of the next text field if the correct number is entered into the previous text field. textfieldTwo background color will change to green if the correct value is entered in textFieldOne. If the value is incorrect then nothing will happen. Please help me out. I have two text fields called textFieldOne and textFieldTwo and nothing else in the code.
Just pop this in your main view controller in an empty project (try using iphone 6 on the simulator)
import UIKit
class ViewController: UIViewController {
var txtField:UITextField!
var txtFieldTwo:UITextField!
var rightNumber = 10
override func viewDidLoad() {
super.viewDidLoad()
//txtFieldOne
var txtField = UITextField()
txtField.frame = CGRectMake(100, 100, 200, 40)
txtField.borderStyle = UITextBorderStyle.None
txtField.backgroundColor = UIColor.blueColor()
txtField.layer.cornerRadius = 5
self.view.addSubview(txtField)
//txtFieldTwo
var txtFieldTwo = UITextField()
txtFieldTwo.frame = CGRectMake(100, 150, 200, 40)
txtFieldTwo.borderStyle = UITextBorderStyle.None
txtFieldTwo.backgroundColor = UIColor.blueColor()
txtFieldTwo.layer.cornerRadius = 5
self.view.addSubview(txtFieldTwo)
txtField.addTarget(self, action: "checkForRightNumber", forControlEvents: UIControlEvents.AllEditingEvents)
self.txtField = txtField
self.txtFieldTwo = txtFieldTwo
}
func checkForRightNumber() {
let number:Int? = self.txtField.text.toInt()
if number == rightNumber {
self.txtFieldTwo.backgroundColor = UIColor.greenColor()
} else {
self.txtFieldTwo.backgroundColor = UIColor.blueColor()
}
}
}
EDIT: Adding a version with IBOutlets and IBActions
Note that in this example the IBAction is connected to txtFieldOne on Sent Events / Editing Changed
Also, make sure your Text Fields border colors are set to None. In the storyboard, the way to do this is to choose the left most option with the dashed border around it. That's so you can color the backgrounds. You can use layer.cornerRadius to set the roundness of the border's edges.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var txtField: UITextField!
#IBOutlet weak var txtFieldTwo: UITextField!
var rightNumber = 10
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func checkForRightNumber(sender: AnyObject) {
let number:Int? = self.txtField.text.toInt()
if number == rightNumber {
self.txtFieldTwo.backgroundColor = UIColor.greenColor()
} else {
self.txtFieldTwo.backgroundColor = UIColor.blueColor()
}
}
}