How to rewrite code for a (too) big IF statement? - swift

I have a project with some UIbuttons with different UIimages displayed in it. Through user interaction, there could be any of the UIimages in the UIButtons. There are like around 1000 images in the project. I have initialised a variable named 'i'. And a IBAction named buttonTapped on all the buttons. Now I want to update variable 'i' and use the value of 'i' for every different possible `UIImage'. I can do this with an IF statement as shown here:
#IBAction func buttonTapped(sender: UIButton) {
if sender.currentImage == UIImage(named: "image1") {
i = 1
print(i)
// use the value of i
} else if sender.currentImage == UIImage(named: "image2") {
i = 2
print(i)
// use the value of i
} else if sender.currentImage == UIImage(named: "image3") {
i = 3
print(i)
// use the value of i
} else if // and so on
But I would like a better solution then an IF statement with around 1000 else if(s). I have tried, but I am not able to rewrite the code in a concise matter. What could I use instead of the IF statement? Some kind of loop?

A crude solution (assuming the indices are all sequential) would be
for i in 1 ... 1000 { // or whatever the total is
if sender.currentImage == UIImage(named: "image\(i)") {
print(i)
// use i
}
}
A better solution, especially if the names are not in the format you give, is to have an array of structs (or just an array of images, if the numbers are all sequential)...
struct ImageStruct {
var image: UIImage
var index: Int
}
var imageStructs:[ImageStruct]... // Some code to fill these
...
#IBAction func buttonTapped(sender: UIButton) {
let matches = self.imageStructs.filter( { $0.image == sender.currentImage } )
if let match = matches.first {
// use match.index
}
}

Related

More elegant way to assign optionals to a lazy property

Imagine we have a lazy imageView and we want to initialize it only if there is an image to assign. So we need to check for the image value each time:
lazy var imageView1 = UIImageView()
lazy var imageView2 = UIImageView()
var image: UIImage?
var someOptional: SomeType?
func addImage1IfNeeded() {
guard let image = image else { return }
imageView1.image = image
}
func addImage2IfNeeded() {
guard let image = someOptional?.someChildOptional?.image else { return }
imageView2.image = image
}
So if we have lots of optionals and lazy variables and some optional chaining situations, we will have thons of repetitive codes.
What is more elegant way to do this? (Maybe using an operator? an extension on optional?)
Please note that This is not for optimization. This example is summarized and the original issue is happening when you want to not adding UI elements if there is no data to present. Also in other situations.
One way is to use an operator like this:
infix operator ?=>
func ?=>(lhs: Any?, rhs: #autoclosure ()->()) {
guard lhs != nil else { return }
rhs()
}
Usage:
image ?=> (imageView1.image = image)
I don't know if we can combine the first and last argument since they are alwayse the same.
Update - Option 2
Following #Sweeper's comment:
func ?=><T>(lhs: T?, rhs: (T)->()) {
guard let lhs = lhs else { return }
rhs(lhs)
}
Usage:
someOptional?.someChildOptional?.image = { imageView2.image = $0 }
It's better for long optional changings but yet requires curly braces and $ argument (that I forgot it's name)
Using keypaths, I've been able to extract what you are doing into a function:
func assignNonNilImage<R, V>(_ root: R, _ keyPath: ReferenceWritableKeyPath<R, V>, _ value: V?) {
if let nonNilValue = value {
root[keyPath: keyPath] = nonNilValue
}
}
Usage:
class Foo {
lazy var imageView = UIImageView()
var image: UIImage?
func f() {
// here!
assignNonNilImage(self, \.imageView.image!, image)
}
}
I'm not sure if it's any "better" though...
You can also write this as a method in your class:
func assignNonNilImage<V>(_ keyPath: ReferenceWritableKeyPath<Foo, V>, _ value: V?) {
if let nonNilValue = value {
self[keyPath: keyPath] = nonNilValue
}
}

Changing UIImage after click

I have 13 images named 1 to 13 in my project. I would like the UIImageView to change from image 1 to 2, then 3, then 4 and so on, at the click of a UIButton.
My current code when the UIButton is clicked:
#IBAction func button(sender: Any) {
cardImage.image = UIImage(named: 1)
}
So I know I need to store the value in a variable and initially set the value to 1. Add 1 to the value every time I click the UIButton. That value needs to be somehow linked to cardImage.Image = UIImage. How can I achieve this?
So create an array of your filenames in your view controller, and an index into that array:
class ViewController: UIViewController {
lazy var filenames: [String] = {
return Array(1...13).map {String($0)}
}()
var filenameIndex = 0
Then write a function to load one of your images and increment the index:
func fetchImage() -> UIImage? {
let result = UIImage(named: filenames[filenameIndex])
filenameIndex = (filenameIndex + 1) % filenames.count
return result
}
(You could also use Yury's approach of managing an integer index and converting that to a filename, but my approach will let you manage cycling through any array of image filenames, whether they are sequential numbers or not.)
You need to store your current image number value in a variable and increase in when the button is pressed.
let imagesCount: Int = 13
var currentImageNumber: Int = 1
#IBAction func didPressButton(sender: Any) {
cardImage.image = UIImage(named: "\(currentImageNumber)")
currentImageNumber += 1
if currentImageNumber > imagesCount {
currentImageNumber = 1
}
}
Or you can use a shorter version without if condition.
let imagesCount: Int = 13
var currentImageNumber: Int = 0
#IBAction func didPressButton(sender: Any) {
cardImage.image = UIImage(named: "\(currentImageNumber + 1)")
currentImageNumber += 1
currentImageNumber %= imagesCount
}

Why is Label Text not Updating to Specified Variable(swift)

mega-noob question:
So right now I'm just trying to test my variable system in my gpa project. When popup button is altered it alters the value of a courses point total for example
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
func UPDTmathG(_ sender: Any) {
if let title = (sender as AnyObject).titleOfSelectedItem, title == "A+" {
mathPoints = 4.3;
}
if let title = (sender as AnyObject).titleOfSelectedItem, title == "A" {
mathPoints = 4;
}
Now to test that these variables are actually changing an learn how to change string values.
testLabel.stringValue = String(mathPoints);
Ideally, when this added to the func UPDTmathG(_ sender: Any) {.
It would update the string value of the label to be the value of the variable, but the string isn't updating. Any ideas?
Thanks:)

In Swift3, combine responds#to and calling in one fell swoop?

For example,
superview?.subviews.filter{
$0 != self &&
$0.responds(to: #selector(setter: Blueable.blue))
}.map{
($0 as! Blueable).blue = false
}
Is there a concept like ..
x.blue??? = false
'???' meaning 'if it responds to blue, call blue'...
Note - I fully appreciate I could write an extension, callIfResponds:to, or a specific extension, blueIfBlueable.
I'm wondering if there's some native swiftyness here, which I don't know about. It seems to be a pretty basic concept.
Footnote:
in the ensuing heated discussion, there is mention of using a protocol. Just for the benefit of anyone reading, here's one approach to using a protocol:
protocol Blueable:class {
var blue:Bool { get set }
}
extension Blueable where Self:UIView {
func unblueAllSiblings() { // make this the only blued item
superview?.subviews.filter{$0 != self}
.flatMap{$0 as? Blueable}
.forEach{$0.blue = false}
}
}
// explanation: anything "blueable" must have a blue on/off concept.
// you get 'unblueAllSiblings' for free, which you can call from
// any blueable item to unblue all siblings (likely, if that one just became blue)
To use it, for example...
#IBDesignable
class UILabelStarred: UILabel, Blueable {
var blueStar: UIView? = nil
let height:CGFloat = 40
let shinyness:CGFloat = 0.72
let shader:Shader = Shaders.Glossy
let s:TimeInterval = 0.35
#IBInspectable var blue:Bool = false {
didSet {
if (blue == true) { unblueAllSiblings() }
blueize()
}
}
func blueize() {
if (blueStar == nil) {
blueStar = UIView()
self.addSubview(blueStar!)
... draw, say, a blue star here
}
if (blue) {
UIView.animate(withDuration: s) {
self. blueStar!.backgroundColor = corporateBlue03
self.textColor = corporateBlue03
}
}
else {
UIView.animate(withDuration: s) {
self. blueStar!.backgroundColor = UIColor.white
self.textColor = sfBlack5
}
}
}
}
Just going back to the original question, that's all fine. But you can't "pick up on" an existing property (a simple example is isHidden) in existing classes.
Furthermore as long as we're discussing it, note that in that example protocol extension, you unfortunately can NOT have the protocol or extension automatically, as it were, call unblueAllSiblings from "inside" the protocol or extension, for exactly this reason: why you can't do it
As others have said, a better way of going about this is by conditionally type-casting rather than using responds(to:). However, don't overlook just using a for in loop – they're pretty powerful, allowing you to use pattern matching and where clauses, allowing you to iterate over a given subset of elements.
for case let blueable as Blueable in superview?.subviews ?? [] where blueable !== self {
blueable.blue = false
}
case let blueable as Blueable uses the type-casting pattern in order to only match elements that are Blueable, and bind them to blueable if successful.
where blueable !== self excludes self from being matched.
Somehow I think what you want is:
superview?.subviews.filter {
$0 != self // filter out self
}.flatMap {
$0 as? Blueable
}.forEach {
$0.blue = false
}
Why should you be checking whether a class conforms to a setter when you can check the type?
Checking selectors should not be used in pure Swift. You will need it mostly for interacting with Obj-C APIs - dynamic methods, optional methods in protocols or informal protocols.
Why not check conformance to the type directly? Something like:
superview?.subviews.forEach {
guard $0 !== self else { return }
($0 as? Blueable)?.blue = false
}
You can add this extension
extension UIView {
var siblings: [UIView] { return self.superview?.subviews.filter { $0 != self } ?? [] }
}
Now pick the solution that you prefer among the followings
Solution 1
siblings.flatMap { $0 as? Blueable }.forEach { $0.blue = false }
Solution 2
siblings.forEach { ($0 as? Blueable)?.blue = false }

How to check if a text field is empty or not in swift

I am working on the code below to check the textField1 and textField2 text fields whether there is any input in them or not.
The IF statement is not doing anything when I press the button.
#IBOutlet var textField1 : UITextField = UITextField()
#IBOutlet var textField2 : UITextField = UITextField()
#IBAction func Button(sender : AnyObject)
{
if textField1 == "" || textField2 == ""
{
//then do something
}
}
Simply comparing the textfield object to the empty string "" is not the right way to go about this. You have to compare the textfield's text property, as it is a compatible type and holds the information you are looking for.
#IBAction func Button(sender: AnyObject) {
if textField1.text == "" || textField2.text == "" {
// either textfield 1 or 2's text is empty
}
}
Swift 2.0:
Guard:
guard let text = descriptionLabel.text where !text.isEmpty else {
return
}
text.characters.count //do something if it's not empty
if:
if let text = descriptionLabel.text where !text.isEmpty
{
//do something if it's not empty
text.characters.count
}
Swift 3.0:
Guard:
guard let text = descriptionLabel.text, !text.isEmpty else {
return
}
text.characters.count //do something if it's not empty
if:
if let text = descriptionLabel.text, !text.isEmpty
{
//do something if it's not empty
text.characters.count
}
Better and more beautiful use
#IBAction func Button(sender: AnyObject) {
if textField1.text.isEmpty || textField2.text.isEmpty {
}
}
another way to check in realtime textField source :
#IBOutlet var textField1 : UITextField = UITextField()
override func viewDidLoad()
{
....
self.textField1.addTarget(self, action: Selector("yourNameFunction:"), forControlEvents: UIControlEvents.EditingChanged)
}
func yourNameFunction(sender: UITextField) {
if sender.text.isEmpty {
// textfield is empty
} else {
// text field is not empty
}
}
if let ... where ... {
Swift 3:
if let _text = theTextField.text, _text.isEmpty {
// _text is not empty here
}
Swift 2:
if let theText = theTextField.text where !theTextField.text!.isEmpty {
// theText is not empty here
}
guard ... where ... else {
You can also use the keyword guard :
Swift 3:
guard let theText = theTextField.text where theText.isEmpty else {
// theText is empty
return // or throw
}
// you can use theText outside the guard scope !
print("user wrote \(theText)")
Swift 2:
guard let theText = theTextField.text where !theTextField.text!.isEmpty else {
// the text is empty
return
}
// you can use theText outside the guard scope !
print("user wrote \(theText)")
This is particularly great for validation chains, in forms for instance. You can write a guard let for each validation and return or throw an exception if there's a critical error.
As now in swift 3 / xcode 8 text property is optional you can do it like this:
if ((textField.text ?? "").isEmpty) {
// is empty
}
or:
if (textField.text?.isEmpty ?? true) {
// is empty
}
Alternatively you could make an extenstion such as below and use it instead:
extension UITextField {
var isEmpty: Bool {
return text?.isEmpty ?? true
}
}
...
if (textField.isEmpty) {
// is empty
}
use this extension
extension String {
func isBlankOrEmpty() -> Bool {
// Check empty string
if self.isEmpty {
return true
}
// Trim and check empty string
return (self.trimmingCharacters(in: .whitespaces) == "")
}
}
like so
// Disable the Save button if the text field is empty.
let text = nameTextField.text ?? ""
saveButton.isEnabled = !text.isBlankOrEmpty()
A compact little gem for Swift 2 / Xcode 7
#IBAction func SubmitAgeButton(sender: AnyObject) {
let newAge = String(inputField.text!)
if ((textField.text?.isEmpty) != false) {
label.text = "Enter a number!"
}
else {
label.text = "Oh, you're \(newAge)"
return
}
}
Maybe i'm a little too late, but can't we check like this:
#IBAction func Button(sender: AnyObject) {
if textField1.text.utf16Count == 0 || textField2.text.utf16Count == 0 {
}
}
Okay, this might be late, but in Xcode 8 I have a solution:
if(textbox.stringValue.isEmpty) {
// some code
} else {
//some code
}
I used UIKeyInput's built in feature hasText: docs
For Swift 2.3 I had to use it as a method instead of a property (as it is referenced in the docs):
if textField1.hasText() && textField2.hasText() {
// both textfields have some text
}
Swift 4.x Solution
#IBOutlet var yourTextField: UITextField!
override func viewDidLoad() {
....
yourTextField.addTarget(self, action: #selector(actionTextFieldIsEditingChanged), for: UIControlEvents.editingChanged)
}
#objc func actionTextFieldIsEditingChanged(sender: UITextField) {
if sender.text.isEmpty {
// textfield is empty
} else {
// text field is not empty
}
}
Swift 4.2
You can use a general function for your every textField just add the following function in your base controller
// White space validation.
func checkTextFieldIsNotEmpty(text:String) -> Bool
{
if (text.trimmingCharacters(in: .whitespaces).isEmpty)
{
return false
}else{
return true
}
}
I just tried to show you the solution in a simple code
#IBAction func Button(sender : AnyObject) {
if textField1.text != "" {
// either textfield 1 is not empty then do this task
}else{
//show error here that textfield1 is empty
}
}
It's too late and its working fine in Xcode 7.3.1
if _txtfield1.text!.isEmpty || _txtfield2.text!.isEmpty {
//is empty
}
Swift 4/xcode 9
IBAction func button(_ sender: UIButton) {
if (textField1.text?.isEmpty)! || (textfield2.text?.isEmpty)!{
..............
}
}
Easy way to Check
if TextField.stringValue.isEmpty {
}