Swift 3 - If else statement based on button state - ios

I am really new to Swift and working on my first project (I have a bit of experience with Javascript and web development). I have run into trouble (going on 4 hours of trying different solutions).
I have an app where when a UIButton is pushed it logs a value to FireBase (ON). When it is pushed a second time it logs (OFF) to the database.
When I make the button change the view.backgroundColor and put if else tied to the colour it works.
But I can't for the life of me figure out how to build my if else based on the state of the button. I have now ended up trying to change the colour of the button itself and tie the if else to that. Which I know is a really messy improper way to go about it.
import UIKit
import Firebase
import FirebaseDatabase
class ViewController: UIViewController {
#IBAction func OnOffButton(_ sender: UITapGestureRecognizer){
if button.backgroundColor == UIColor.white {
OnOff(state:"ON")
OnOffButton.backgroundColor = UIColor.blue
} else{
OnOff(state:"OFF")
OnOffButton.backgroundColor = UIColor.white
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
OnOff(state: "Off")
OnOffButton.backgroundColor = UIColor.white
}

UIButton inherits from UIControl which has an isSelected property. Use this to track state. Typically you'll use button.isSelected == true to correspond to your on state, and false is your off state.
To toggle state you can use button.isSelected = !button.isSelected.
For your specific example:
// User pressed the button, so toggle the state:
button.isSelected = !button.isSelected
// Now do something with the new state
if button.isSelected {
// button is on, so ...
} else {
// button is off, so...
}

Here’s a quick and dirty implementation of Duncan C’s suggestion to use a buttonIsSelected variable to store the button state:
import UIKit
class ViewController: UIViewController {
var buttonIsSelected = false
#IBOutlet weak var onOffButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
updateOnOffButton()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func onOffButtonTapped(_ sender: Any) {
buttonIsSelected = !buttonIsSelected
updateOnOffButton()
}
func updateOnOffButton() {
if buttonIsSelected {
onOffButton.backgroundColor = UIColor.blue
}
else {
onOffButton.backgroundColor = UIColor.white
}
}
}

Par's solution should work. However, I would suggest a different approach.
In general it's not good design to store state in a view object. It's fragile, and doesn't follow the MVC design pattern, where view objects display and collect state information, not store it.
Instead I would create an instance variable in your view controller, buttonIsSelected. Toggle that when the button is tapped, and have it change the state of the button's selected property, the button's color, AND log the new state to FireBase.
If you store more complex state in your view controller it would be worth separating that out into a model object. It can be as simple as a struct that holds the different state values. That way you have a clear separation between your controller (view controller) and model.

Related

Customize a button and its highlighted state in Swift3 using a class

I'm new to Swift and I assume this is a fundamental question to programming for iOS.
I have three buttons in my storyboard and I want to customize how those buttons look if pressed once, twice and three times.
I also have three themes (pink, blue and orange). What I thought of doing is to create three new classes called pink,blue and orange.swift
I don't want to create them programmatically, only style them programmatically.
What I lack to understand is how do I call the function (Example: "ButtonIsPressed") from my pink.swift class into my #IBAction and #IBOutlet in the main view controller that is also object oriented (ie. I don't want to create a function for every button)?
I can't really find a decent and up-to-date Swift 3 Tutorial for this, any help or advice on this topic will be greatly appreciated.
Why can it not be as simple as?:
#IBAction func buttonPressed(_ sender: UIButton!) {
self.backgroundColor = myPinkCGolor
}
I think shallowThought's answer will work for changing backgroundColor based on button state of a specifically named IBOutlet.
I have three buttons in my storyboard and I want to customize how those buttons look if pressed once, twice and three times.
If you want to maintain "state", as in have a "counter" for how many times a button's been clicked or tapped, you can use the "tag" property of the button. Set it to zero, and in your IBAction functions increment it. (Like shallowThought said, use .touchUpInside and .touchDown for the events.)
Also, you have one minor - but important! - thing wrong in your code Brewski:
#IBAction func buttonPressed(_ sender: UIButton!) {
self.backgroundColor = myPinkCGolor
}
Should be:
#IBAction func buttonPressed(_ sender: UIButton!) {
sender.backgroundColor = myPinkCGolor
}
So combining everything - up vote to shallowThought (also, changing his AnyObject to UIButton and making it Swift 3.x syntax on the UIColors - and would end up with this. Note that there is no need for an IBOutlet, and you can wire everything up in IB without subclassing:
// .touchUpInside event
// can be adapted to show different color if you want, but is coded to always show white color
#IBAction func buttonClicked(sender: UIButton) {
sender.backgroundColor = UIColor.whiteColor()
}
// .touchDown event
// will show a different color based on tap counter
#IBAction func buttonReleased(sender: UIButton) {
switch sender.tag {
case 1:
sender.backgroundColor = UIColor.blue
case 2:
sender.backgroundColor = UIColor.red
case 3:
sender.backgroundColor = UIColor.green
default:
sender.backgroundColor = UIColor.yellow
}
sender.tag += 1
}
There is no methode to set the backgroundColor for a certain state, like there is for other UIButton properties, so you have to listen to the buttons actions:
class ViewController: UIViewController {
#IBOutlet weak var button: UIButton!
#IBAction func buttonClicked(sender: AnyObject) { //Touch Up Inside action
button.backgroundColor = UIColor.whiteColor()
}
#IBAction func buttonReleased(sender: AnyObject) { //Touch Down action
button.backgroundColor = UIColor.blueColor()
}
...
}
or set a unicolor image withimage:UIImage, forState:.selected.

How to add property to UIButton?

thanks for all help:)! fixed it using iboutlet collection and add properies on viewDidLoad
I'm trying to add properties to keyboard keys like layer.shadowColor or layer.shadowRadius.
I got an error
'Value of type '(UIButton)' -> () has no member 'layer'
how to fix this ?
this is my code keyboardViewController.swift
import UIKit
class KeyboardViewController: UIInputViewController {
var newKeyboardView: UIView!
#IBAction func keyPressed(sender: UIButton) {
}
#IBOutlet var nextKeyboardButton: UIButton!
override func updateViewConstraints() {
super.updateViewConstraints()
// Add custom view sizing constraints here
}
override func viewDidLoad() {
super.viewDidLoad()
loadInterface()
}
func loadInterface() {
// load the nib file
let keyboardNib = UINib(nibName: "newKeyboard", bundle: nil)
// instantiate the view
newKeyboardView = keyboardNib.instantiateWithOwner(self, options: nil)[0] as! UIView
// add the interface to the main view
view.addSubview(newKeyboardView)
// copy the background color
view.backgroundColor = newKeyboardView.backgroundColor
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated
}
override func textWillChange(textInput: UITextInput?) {
// The app is about to change the document's contents. Perform any preparation here.
}
override func textDidChange(textInput: UITextInput?) {
// The app has just changed the document's contents, the document context has been updated.
var textColor: UIColor
let proxy = self.textDocumentProxy
if proxy.keyboardAppearance == UIKeyboardAppearance.Dark {
textColor = UIColor.whiteColor()
} else {
textColor = UIColor.blackColor()
}
self.nextKeyboardButton.setTitleColor(textColor, forState: .Normal)
}
}
I think that in order to apply some style to the button, you need an outlet to this button.
Right now, from what I can understand, you are trying to apply styles to the button from the #IBAction to the sender, which is not the proper way to do it.
Try to make an outlet to the button in the view controller and then to apply the styles from within the viewDidLoad method.
I hope this is clear, but if you want a more specific answer you need to show us what you tried, for example pasting the code you have in the view controller
EDIT:
Based on the code you post, the keyboard is a Nib you instantiate from loadInterface(). I don't have a clear vision of the whole thing with only this piece of code, but it seems to me that you are trying to apply some styles to every key button of a keyboard view. Unfortunately this really depends on how the keyboard is implemented, can you provide some more details?
Anyway, from what I see I think you didn't write this code: probably you are following a tutorial or maintaining someone else's code. That's ok, but I suggest you to follow a an introduction course to iOS development with Swift, like the Udacity's one, which is fantastic IMHO (https://www.udacity.com/course/intro-to-ios-app-development-with-swift--ud585)
If you try to format your UIButton with QuartzCore framework, you'll need to import it first:
import QuartzCore
Then you will be able to access those members.
For example (latest swift3 code):
#IBAction func keyPressed(sender: UIButton) {
let button = sender as UIButton!
button?.backgroundColor = UIColor.red
button?.layer.shadowColor = UIColor.black.cgColor
button?.layer.shadowRadius = 1.0
button?.layer.cornerRadius = 4.0
}
In case you need to apply your styles sooner, try to consider to put this code into viewDidLoad or viewDidAppear methods:
self.nextKeyboardButton.backgroundColor = UIColor.red
self.nextKeyboardButton.layer.shadowColor = UIColor.black.cgColor
self.nextKeyboardButton.layer.shadowRadius = 1.0
self.nextKeyboardButton.layer.cornerRadius = 4.0
Seems like you're trying to "add property" not to a button, but rather to a closure which accepts a button as an argument.
Make it like this:
nextKeyboardButton.layer.shadowColor = UIColor.redColor.cgColor
nextKeyboardButton.layer.shadowRadius = 5.0

Multiple Navigation Bar colours for a complex storyboard

I have 48 view controllers in my storyboard / project. I wish to have 2 different types of Navigation Bar designs.
Style 1 is a navigation bar and a status bar that is white and grey (colours not important to the question)
Style 2 is a navigation bar without a status bar. This is black.
I have set style 1 in the app delegate and set style 2 within one of the views. To a point this works and style 2 overwrites style 1. However, when I navigate away from the view, style 2 continues to override.
I could set each view controller explicitly but with 48 views and 4 or 5 lines of code to define the style is seems inefficient. If I later choose to change the style I then have 48 instances of code to edit.
My major experience is with PHP and if I had this situation I would make an include statement to reference style1 or style2 as needed.
I have tried to create a function in Swift to call the desired design but I cannot get it work as it doesn't reference the UIViewController in the same way you would when adding it directly. I have only been coding for Swift / Xcode for 3 months so it could my lack of knowledge.
I would like to find a solution that on each view I can call something like below (PseudoCode)
override func viewWillAppear(animated: Bool) {
navBarStyle1() or navBarStyle2()
}
I have not included my code to adjust the colours as I feel this is not needed for the answer.
What would be the best way to manage this efficiently? Is there an equivalent to a PHP include? If the solution is a function, could you provide an example? Or may be the solution is something different?
As requested, here is one of my view controllers:
import UIKit
class DeleteMatchViewController: UIViewController {
var idPass = ""
// OUTLETS
#IBOutlet weak var errorMessage: UILabel!
#IBOutlet weak var information: UILabel!
// ACTIONS
#IBAction func deleteMatch(sender: UIBarButtonItem) {
// connect and delete from server
// delete from core data
// load from core data
let urlParameters = "removed for privacy"
let status = sendSeverV2("\(apiUrl)/removedforprivacy.php", parameters: urlParameters)
if status == "OK"
{
// DELETESINGLE firstname|David
myDatabase("Matches", theCommand: "DELETESINGLE", theQuery: "userid|\(idPass)")
myDatabase("Messages", theCommand: "DELETEMULTIPLE", theQuery: "people|\(userId)-\(idPass)")
myDatabase("Messagesunsent", theCommand: "DELETEMULTIPLE", theQuery: "people|\(userId)-\(idPass)")
// core data
loadMatchesFromCoreData()
// segue to matches table
performSegueWithIdentifier("jumpMatches", sender: nil)
}
if status == "Error"
{
errorMessage.text = "Connection error"
}
if status == "Security"
{
errorMessage.text = "Authentication error"
authError = "yes"
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewWillAppear(animated: Bool) {
self.navigationController?.navigationBarHidden = false
self.tabBarController?.tabBar.hidden = true
errorMessage.text = ""
information.text = "If you delete this match all messages will be erased and only a future mutual match will all you to contact them again."
}
}
A little OOP can go a long way here.
First, let's make some base view controllers:
class GrayStatusBarViewController: UIViewController {
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// set up the appearance
}
}
class BlackStatusBarViewController: UIViewController {
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
// set up the appearance
}
}
Now, we can make one of these for every appearance we want, and we can move any shared behavior into these base classes we want. It probably makes sense for your app to have a BaseViewController which the two classes I just posted inherit from (rather than inheriting from UIViewController directly).
Then, all you have to do is make your pile of view controllers inherit from the correct controller based on theme.
class DeleteMatchViewController: GrayStatusBarViewController {
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated) // <-- this is super important
// the rest of the view will appear code for this VC
}
}
Alternatively, you can encapsulate the appearance logic in methods in a class like I previously mentioned, the BaseViewContoller, something like this:
class BaseViewController: UIViewController {
func setUpGrayStatusBar() {
// write that logic
}
func setUpBlackStatusBar() {
// write that logic
}
// etc, as many of these as you want
}
And now, you inherit from this class and call the appropriate method:
class DeleteMatchViewController: BaseViewController {
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
setUpGrayStatusBar()
}
}
When it comes to the run time performance of your application, neither of these solutions is any different from simply copy & pasting the same code into all of your individual view controllers. Just because you didn't paste it in more than one place doesn't mean it doesn't run more than once.

How can I maintain IBOutlet variable data when switching between views

Short version:
How can I "snapshot" my game's status when leaving that view and restore it in its exact state when going back into the view? Or simply maintain the values regardless of my current view?
Long version:
I have a "settings" view with its own view controller and a "game" view with its own view controller. I'd like to be able to switch back to settings to make a change to the game without reinitializing all my IBOutlets and their values in my game view when I go back.
I understand that it's where the variables are declared and thus will have to reinitialize when called but I even tried defining them in their declaration with global variables that I can update on "viewDidDisappear" but the values/attributes don't seem to populate. I would think there has to be a way to retain these values (or the games current status) in memory without deallocating when the view disappears.
I've added some sample code wherein the issue can be replicated though I don't think it's so much a problem with the code as it with my limited understanding of its functionality and/or capabilities.
Thanks in advance for any help that leads to a solution!
I have a custom class for my game pieces:
class gamePiece: UILabel {
var face = "face"
func updateFace() {
self.text = self.face
}
}
var isGameOver = true
var allLabels = [gamePiece]()
My gameViewController looks like this:
class gameViewController: UIViewController {
#IBOutlet var l1: gamePiece!
#IBOutlet var l2: gamePiece!
#IBOutlet var l3: gamePiece!
#IBOutlet var l4: gamePiece!
#IBAction func buttonPressed() {
l1.face = "hello world"
l1.updateFace()
}
override func viewDidLoad() {
super.viewDidLoad()
if isGameOver {
allLabels = [l1, l2, l3, l4]
isGameOver = false
} else {
l1.text = allLabels[0].text!
l2.text = allLabels[1].text!
l3.text = allLabels[2].text!
l4.text = allLabels[3].text!
allLabels = [l1, l2, l3, l4]
}
for label in allLabels {
label.updateFace()
}
// Do any additional setup after loading the view, typically from a nib.
}
override func viewDidDisappear(animated: Bool) {
allLabels = [l1, l2, l3, l4]
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
The expectation is that l1.text would render "hello world". Instead it renders "face"; the default value of an object of that my gamePiece class.
I'm sure this is a simple fix.. I just think it's so close to my face I can't see it.

Click button in a UIViewController that was loaded a subView (UIView)

I'm trying to add a UIView subview into a UIViewController, and that UIView has a UISwitch that I want the user to be able to toggle. Based on the state, a UITextField's value will toggle back and forth. Here is the subview (InitialView):
import UIKit
class InitialView: UIView {
// All UI elements.
var yourZipCodeSwitch: UISwitch = UISwitch(frame: CGRectMake(UIScreen.mainScreen().bounds.width/2 + 90, UIScreen.mainScreen().bounds.height/2-115, 0, 0))
override func didMoveToSuperview() {
self.backgroundColor = UIColor.whiteColor()
yourZipCodeSwitch.setOn(true, animated: true)
yourZipCodeSwitch.addTarget(ViewController(), action: "yourZipCodeSwitchPressed:", forControlEvents: UIControlEvents.TouchUpInside)
self.addSubview(yourZipCodeSwitch)
}
}
If I want to have it's target properly pointing at the below function, where should I either set the target or include this function? I tried:
Setting the target in the UIViewController instead of the UIView
Keeping the function in the UIView
Here's the function:
// Enable/disable "Current Location" feature for Your Location.
func yourZipCodeSwitchPressed(sender: AnyObject) {
if yourZipCodeSwitch.on
{
yourTemp = yourZipCode.text
yourZipCode.text = "Current Location"
yourZipCode.enabled = false
}
else
{
yourZipCode.text = yourTemp
yourZipCode.enabled = true
}
}
And here is where I'm loading it into the UIViewController:
// add initial view
var initView : InitialView = InitialView()
// Execute on view load
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
view.addSubview(initView)
}
Any help is much appreciated - thanks!
Yeah, the didMoveToSuperView() placement doesn't make much sense. So you're creating a random, totally unconnected ViewController instance to make the compiler happy but your project sad. Control code goes in controllers, view code goes in views.
You need in your real ViewController:
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(initView)
// Note 'self' is the UIViewController here, so we got the scoping right
initView.yourZipCodeSwitch.addTarget(self, action: "yourZipCodeSwitchPressed:", forControlEvents: .ValueChanged)
}
Also, .TouchUpInside is for UIButtons. Toggle switches are much more complicated, so their events are different. Touching up inside on a toggle switch's current setting can and should do nothing, whereas touchup inside on the opposite setting triggers the control event above. iOS does all the internal hit detection for you.

Resources