Calculate a price with 3 steppers - swift

I have created an application that displays the prices of bus tickets.
I use 3 steppers to increment the prices. There are the teenagers prices at $0.75, the seniors prices at $2.00, and the adults prices at $3.00. The goal is to be able to add and calculate teen, senior, and adult prices in the same label.
My current code only allows me to calculate for one category at a time. But I can't combine and calculate all 3 price categories in the same label.
Here is my code:
#IBOutlet weak var seniorsTicketsStepper: UIStepper!
#IBOutlet weak var adultsTicketsStepper: UIStepper!
#IBOutlet weak var teenagersTicketsStepper: UIStepper!
#IBOutlet weak var numOfTickets: UILabel!
#IBOutlet weak var ticketsPrices: UILabel!
var totalPrice: Double = 0.00
#IBAction func seniorsTicketsTapped(_ sender: UIStepper) {
updateTicketsCount(stepper: sender)
updateTicketsPrices(stepper: sender)
}
#IBAction func adultsTicketsTapped(_ sender: UIStepper) {
updateTicketsCount(stepper: sender)
updateTicketsPrices(stepper: sender)
}
#IBAction func teenagersTicketsTapped(_ sender: UIStepper) {
updateTicketsCount(stepper: sender)
updateTicketsPrices(stepper: sender)
}
func updateTicketsCount(stepper: UIStepper) {
stepper.maximumValue = 10
let summe: Int = Int(seniorsTicketsStepper.value + adultsTicketsStepper.value + teenagersTicketsStepper.value)
if stepper.value < 1 {
numOfTickets.text = summe.description
validateButton.isEnabled = false
validateButton.backgroundColor = UIColor.init(red: 170/250, green: 170/250, blue: 170/250, alpha: 1)
} else {
numOfTickets.text = summe.description
validateButton.isEnabled = true
validateButton.backgroundColor = UIColor.init(red: 0/250, green: 194/250, blue: 166/250, alpha: 1)
}
}
func updateTicketsPrices(stepper: UIStepper) {
var price = totalPrice
if teenagersTicketsStepper.value != 0 {
price += 0.75
}
if seniorsTicketsStepper.value != 0 {
price += 2.00
}
if adultsTicketsStepper.value != 0 {
price += 3.00
}
price = price * stepper.value
ticketsPrices.text = "$ \(price.description)"
}

But I can't combine and calculate all 3 price categories in the same label.
Because you don't have three price categories. In fact, you have no prices at all. You have only interface objects. You've completely omitted to give your view controller a place to store data.
Basically, you're storing your data in the interface, which is totally wrong. The interface is for displaying data. Storing data is something your code (meaning your objects) must do.
Take some time and think about what your program actually does — not what the user does or sees, but what the program's reasoning would be even if there were no user interface at all. What information does it manipulate? Once you've got instance properties for all the information you're trying to keep track of, the answer to your problem will fall into your lap.
A further hint: code like this is completely wrong:
if teenagersTicketsStepper.value != 0 {
price += 0.75
}
if seniorsTicketsStepper.value != 0 {
price += 2.00
}
if adultsTicketsStepper.value != 0 {
price += 3.00
}
Once you've got your categories, perhaps even expressed as an object type, that sort of "choice" will happen without magic numbers being embedded in tedious logic.

Related

Programmatically emptying UIStackView

I have a fairly simple code which, upon clicking a button, adds a randomly colored UIView to a UIStackView, and upon a different button click, removes a random UIView from the UIStackView.
Here's the code:
import UIKit
class ViewController: UIViewController, Storyboarded {
weak var coordinator: MainCoordinator?
#IBOutlet weak var stackView: UIStackView!
var tags: [Int] = []
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func buttonPressed(_ sender: UIButton) {
switch sender.tag {
case 10:
let view = UIView(frame: CGRect(x: 0, y: 0, width: stackView.frame.width, height: 20))
var number = Int.random(in: 0...10000)
while tags.contains(number) {
number = Int.random(in: 0...10000)
}
tags.append(number)
view.tag = number
view.backgroundColor = .random()
stackView.addArrangedSubview(view)
case 20:
if tags.count == 0 {
print("Empty")
return
}
let index = Int.random(in: 0...tags.count - 1)
let tag = tags[index]
tags.remove(at: index)
if let view = stackView.arrangedSubviews.first(where: { $0.tag == tag }) {
stackView.removeArrangedSubview(view)
}
default:
break
}
}
}
extension CGFloat {
static func random() -> CGFloat {
return CGFloat(arc4random()) / CGFloat(UInt32.max)
}
}
extension UIColor {
static func random() -> UIColor {
return UIColor(
red: .random(),
green: .random(),
blue: .random(),
alpha: 1.0
)
}
}
I'm not using removeFromSuperview on purpose - since I would (later) want to reuse those removed UIViews, and that is why I'm using removeArrangedSubview.
The issue I'm facing is:
All UIViews are removed as expected (visually of course, I know they're still in the memory) until I reach the last one - which, even though was removed, still appears and filling the entire UIStackView.
What am I missing here?
You can understand removeArrangedSubview is for removing constraints that were assigned to the subview. Subviews are still in memory and also still inside the parent view.
To achieve your purpose, you can define an array as your view controller's property, to hold those subviews, then use removeFromSuperview.
Or use .isHidden property on any subview you need to keep it in memory rather than removing its contraints. You will see the stackview do magical things to all of its subviews.
let subview = UIView()
stackView.addArrangedSubview(subview)
func didTapButton(sender: UIButton) {
subview.isHidden.toggle()
}
Last, addArrangedSubview will do two things: add the view to superview if it's not in superview's hierachy and add contraints for it.

Swift Timer Loop doesn't trigger but view and controller are connected

I'm trying to print the title "⚡️FlashChat" one letter at a time.
I'm coding along with a video and I've checked my code against the instructor's and it matches.
Right now, the title prints in its entirety when the sim is built.
I've adjusted the withTimeInterval to see fit that was set too quickly.
If I remove the loop and just execute the titleLabel.text = "", the title disappears, so it is connected correctly.
Thanks in advance!
import UIKit
class WelcomeViewController: UIViewController {
#IBOutlet weak var titleLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
titleLabel.text = ""
var charIndex = 0.0
let titleText = "⚡FlashChat"
for letter in titleText {
Timer.scheduledTimer(withTimeInterval: 7.0
* charIndex, repeats: false) {(timer)
in
self.titleLabel.text?.append(letter)}
}
charIndex += 1
}
}
You're incrementing charIndex outside your for loop. So interval will always be equal to 0

AKMIDIListener: ReceivedNoteOn unable to change UI Components

I am creating an application that receives MIDI notes and plays music in the app according to the note it received. I am using AudioKit's AKMIDIListener protocol and the function receivedMIDINoteOn. It am able to recieve these events and play the notes that I want to play from my app accordingly. However, I am also trying to change UI components within the ReceivedMIDINoteOn function, but I am unable to do so. Otherwise, the receivedMIDINoteOn is handling it's events correctly, the UI simply won't change to reflect this for some reason. The UIViewController that I am changing also has the AKMIDIListener protocol, and the receivedMIDINoteOn function is also defined in that UIViewController. I have provided a code snippet below to help ID the problem. Why might the UI components not update? In the code below, the "playButton" is not being updated when it receives the MIDINoteOn function, even though I know the function runs because it plays the background music. Also, the playButton changes it's title accordingly whenever I use change the title in some other function in my code
let midi = AudioKit.midi
class firstPageViewController: UIViewController, AKMIDIListener {
#IBOutlet var MusicButtons: [UIButton]!
#IBOutlet var playButton: UIButton!
#IBOutlet var pauseButton: UIButton!
#IBOutlet var WaveForm: AudioVisualizationView!
#IBOutlet weak var firstButton: UIButton!
#IBOutlet weak var secondButton: UIButton!
#IBOutlet weak var thirdButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
isOnFirstPage = true
isOnSecondPage = false
isOnThirdPage = false
self.secondButton.setTitleColor(UIColor.blue, for: .normal)
self.thirdButton.setTitleColor(UIColor.blue, for: .normal)
midi.openInput()
midi.addListener(self)
self.init_buttons()
self.init_soundwave()
self.init_audio()
}
func receivedMIDINoteOn(noteNumber: MIDINoteNumber, velocity: MIDIVelocity, channel: MIDIChannel) {
let currNum = mapping[Int(noteNumber)]
if currNum == 64 { //Play button
self.playBackground(0)
self.playButton.setTitle("Currently Playing", for: .normal)
}
else if currNum == 67 { //Pause button
self.pauseMusic(0)
self.playButton.setTitle("Play", for: .normal)
}
else if currNum == 68 { //Switch button - first page
self.firstPageSwitch(0)
}
else if currNum == 69 { //Switch button - second page
self.secondPageSwitch(0)
}
else if currNum == 70 { //Switch button - third page
self.thirdPageSwitch(0)
}
else if currNum == 71 {
//
}
else if currNum == 65 {
//
}
else if currNum == 66 {
//
}
else {
if isOnFirstPage! {
self.playMusic(currNum: currNum!, holder: audioPlayer)
self.MusicButtons![currNum!].backgroundColor = UIColor(red: 247/255, green: 183/255, blue: 98/255, alpha: 1.0)
}
if isOnSecondPage!{
self.playMusic(currNum: currNum!, holder: secondAudioPlayer)
self.MusicButtons![currNum!].backgroundColor = UIColor(red: 247/255, green: 183/255, blue: 98/255, alpha: 1.0)
}
}
}
}
The commenter is correct, you need wrap your UI calls in a
DispatchQueue.main.async {
// ... code here
}
And also perhaps will have to put an self.playButton.setNeedsDisplay() at the end.

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()
}
}
}

Tic tac toe finding win?

I am making a tic tac toe game in Swift for 2 players. Now I know I can find a winner by making outlets for the 9 buttons and then writing for all the 3 in row cases, but I was wondering if there is a better way to do this? And is there a better way to write my switch statement?
#IBOutlet weak var button1: UIButton!
#IBOutlet weak var button2: UIButton!
#IBOutlet weak var button3: UIButton!
#IBOutlet weak var button4: UIButton!
#IBOutlet weak var button5: UIButton!
#IBOutlet weak var button6: UIButton!
#IBOutlet weak var button7: UIButton!
#IBOutlet weak var button8: UIButton!
#IBOutlet weak var button9: UIButton!
var player = 1
#IBAction func playersTurn(sender: UIButton){
switch player{
case 1: sender.setTitle("X", forState: UIControlState.Normal); player = 2;break;
case 2: sender.setTitle("O", forState: UIControlState.Normal); player = 1;break;
default:exit(0); break
}
}
I would set the tag property on your buttons using values 0-9 in order from top to bottom, left to right, like so:
[ 0 ][ 1 ][ 2 ]
[ 3 ][ 4 ][ 5 ]
[ 6 ][ 7 ][ 8 ]
Then create a model to represent each square and give it a corresponding index value, as well as a reference to the player who owns it. Then you can handle you game logic with those models and update the UI by getting references to your buttons with viewForTag(_:). Here's my version:
class Square {
var owningPlayer: Player?
let index: Int
init( index: Int ) {
self.index = index
}
}
class Player {
let symbol: String
init( symbol: String ) {
self.symbol = symbol
}
}
class ViewController: UIViewController {
var squares = [Square]()
var players = [Player]()
var currentPlayer: Player?
override func viewDidLoad() {
super.viewDidLoad()
self.players = [ Player(symbol: "X"), Player(symbol: "O") ]
self.currentPlayer = self.players[0]
// Create squares
for i in 0..<9 {
self.squares.append( Square(index: i) )
}
}
#IBAction func buttonPressed( button: UIButton ) {
guard let player = self.currentPlayer else {
fatalError( "Don't have a current player!" )
}
if let square = self.squares.filter({ $0.index == button.tag }).first {
// Update model
square.owningPlayer = player
// Update UI
button.setTitle( player.symbol, forState: .Normal )
}
if self.checkWin() {
print( "\(player.symbol) wins!" )
}
else {
self.currentPlayer = self.players.filter({ $0 !== player }).first
}
}
func checkWin() -> Bool {
guard let player = self.currentPlayer else {
fatalError( "Don't have a current player!" )
}
let winningSequences = [
// Horizontal rows
[ 0, 1, 2 ],
[ 3, 4, 5 ],
[ 6, 7, 8 ],
// Diagonals
[ 0, 4, 8 ],
[ 2, 4, 6 ],
// Vertical rows
[ 0, 3, 6 ],
[ 1, 4, 7 ],
[ 2, 5, 8 ],
]
// Get indexes owned by this player
let selectedIndexes = self.squares.filter({ $0.owningPlayer === player }).map { $0.index }
// Change the sequence arrays into sets for accurate comparison using `contains(_:)`
if winningSequences.map({ Set<Int>($0) }).contains( Set<Int>(selectedIndexes) ) {
return true
}
return false
}
}
Although #Patrick Lynch's answer is excellent I found that it didn't match a win should the game go beyond 3 moves.
This is because he is only checking if any of the winningSequences contains a current move sequence (selectedIndexes). As the winningSequences map only has a max of 3 items it'll never contain a set of 4 moves.
To resolve this I changed contains to subset. i.e is a winningSequence (0, 1, 2) a subset of the selectedIndexes (0, 5, 1, 2)
Replace
if winningSequences.map({ Set<Int>($0) }).contains( Set<Int>(selectedIndexes) ) {
return true
}
with
for seq in winningSequences.map( { Set<Int>($0)}) {
if seq.isSubset(of: selectedIndexes) {
return true
}
}
I'm still new to swift so if I'm wrong or anyone has a better idea then I'd be happy to hear it.