I have two types of table cells, one that has 6 buttons, and each button has a number value, and then a second cell that has a button to calculate the total sum of the selected numbers, and a label to display this total sum in.
My issue is I cannot get the label to calculate the total. Here is what I have so far
Number Cell:
protocol ToggleNumberCellDelegate: AnyObject {
/// This method detects the selected value of the cell.
func toggleNumberCell(_ toggleNumberCell: ToggleNumberCell, selectedValue: Int)
}
class ToggleNumberCell: UITableViewCell {
static let reuseIdentifier = String(describing: ToggleNumberCell.self)
#IBOutlet private weak var titleLabel: UILabel!
#IBOutlet private weak var zeroButton: UIButton!
#IBOutlet private weak var oneButton: UIButton!
#IBOutlet private weak var twoButton: UIButton!
#IBOutlet private weak var threeButton: UIButton!
#IBOutlet private weak var fourButton: UIButton!
#IBOutlet private weak var fiveButton: UIButton!
#IBOutlet private weak var sixButton: UIButton!
private weak var delegate: ToggleNumberCellDelegate?
private var value: Int?
//...
#IBAction func buttonTapped(_ sender: UIButton) {
switch sender {
case zeroButton:
self.zeroButton.backgroundColor = UIColor.NBABlue
self.zeroButton.tintColor = UIColor.white
self.value = 0
self.delegate?.toggleNumberCell(self, selectedValue: self.value!)
print("The value you tapped is \(value)")
case oneButton:
self.oneButton.backgroundColor = UIColor.NBABlue
self.oneButton.tintColor = UIColor.white
self.value = 1
self.delegate?.toggleNumberCell(self, selectedValue: self.value!)
print("The value you tapped is \(value)")
case twoButton:
self.twoButton.backgroundColor = UIColor.NBABlue
self.twoButton.tintColor = UIColor.white
self.value = 2
self.delegate?.toggleNumberCell(self, selectedValue: self.value!)
print("The value you tapped is \(value)")
The above code just sets up the buttons, and gives each a value by using its delegate.
Label Cell
class CalculateCell: UITableViewCell, ToggleNumberCellDelegate {
var increment = 0
static let reuseIdentifier = String(describing: CalculateCell.self)
#IBOutlet private weak var calculateButton: UIButton!
#IBOutlet private weak var totalLabel: UILabel!
func configure(answer: AnswerModel) {
self.backgroundColor = UIColor.secondarySystemGroupedBackground
self.totalLabel.text = answer.text
self.totalLabel.font = .preferredFont(forTextStyle: .headline)
calculateButton.layer.cornerRadius = 10.0
}
override func prepareForReuse() {
super.prepareForReuse()
self.totalLabel.text = nil
}
#IBAction func calculateTapped(_ sender: UIButton) {
// You need to get the selected value in here somehow.
}
func toggleNumberCell(_ toggleNumberCell: ToggleNumberCell, selectedValue: Int) {
increment = selectedValue
}
}
Here I called the delegate to get the selected value, but I think I did this wrong, can someone tell me how I can calculate to total value?
Here is a screenshot of what I want to achieve.
You have a basic problem with your approach. You should not store data in table view cells. They are views, and should display data, not store data.
You should set up a model object to store the state of your UI. (it could be as simple as an array of integers, one of the value of each ToggleNumberCell in your table view.)
You should have a controller object serve as the delegate for the ToggleNumberCell. When its toggleNumberCell method gets called, it would update the count for that cell's entry in your data model, and then tell the total cell to update itself. Your table view data source would query the model and use the value for each entry in the model to calculate the totals, then install that value in the total cell.
I would recommend you make the UIViewController (than manages the UITableView) the ToggleNumberCellDelegate. Then that view controller can keep track of the total of all of the buttons. The view controller then can provide the total to
the CalculateCell. This separates the logic between the cells. Ideally the controller manages the logic, while the cells are simply views displaying the data.
Also, I would change the toggleNumberCell function to the following:
func toggleNumberCell(_ toggleNumberCell: ToggleNumberCell, selectedValue: Int) {
// Using += will result in adding to the existing value instead of overwriting it
increment += selectedValue
}
Related
I have 4 different progressView
#IBOutlet weak var firstProgressBar: UIProgressView!
#IBOutlet weak var secondProgressBar: UIProgressView!
#IBOutlet weak var thirdProgressBar: UIProgressView!
#IBOutlet weak var fourthProgressBar: UIProgressView!
And I have an array with 4 values
[30,20,24,25]
Since this array change from the API call (I need to check if I really have this value), what is the best and safer way to:
Order this array from bigger to smaller
Iterate throught this array and set each of my progressView value to the corresponding array value
This is my current code:
func setupProgressBar(item: CustomObject) {
let array = item.array // [x,y,z,w] array
self.progressBar.setProgress( ?? , animated: false)
}
I have an animation for the value but it's not the point of my question, i just need to set values
A better approach would be to connect all the UIProgressBar in your storyboard to an #IBOutlet collection.
#IBOutlet weak var progressBars: [UIProgressView]!
Then iterate through the array to set the value of progressBars.
func setupProgressBar(item: CustomObject) {
let array = item.array // [x,y,z,w] array
for (progressBar, value) in zip(progressBars, array) {
progressBar.setProgress(value, animated: false)
}
}
func setupProgressBar(item: CustomObject) {
let valueArray = item.array.sorted // [x,y,z,w] array
let progressBars = [firstProgressBar, second….., fourthProgressBar]
for item in valueArray.enumerated() {
progressBars[item.index].setProgress(item.value)
}
}
Make sure that progress bars and values are equal. Otherwise the code will crash.
Problem I've found some questions asking how to disable a particular cell button in a table view, but what I want to do is to disable all instances of a button within a table view cell when another button is pressed.
Details I have a table view which is displaying a list of exercises and number of reps in a custom cell. Within the custom cell is also a button "swap" which allows a user to swap an exercise for another one before the workout starts.
When the user hits "start workout" (which triggers a timer) I want to disable all instances of the swap button (grey them all out and make non clickable).
Code
My workout cell class is here :
class WorkoutCell : UITableViewCell {
var delegate: WorkoutCellDelegate?
#IBAction func swapButtonPressed(_ sender: Any) {
delegate?.swapButtonTapped(cell: self)
}
#IBOutlet weak var exerciseName: UILabel!
#IBOutlet weak var repsNumber: UILabel!
}
protocol WorkoutCellDelegate {
func swapButtonTapped(cell: WorkoutCell)
}
What have I tried
The way I thought to do this was to add an IBOutlet (e.g. 'swapButton') for the button and then simply do something like :
#IBAction func startButtonPressed(_ sender: UIButton) {
WorkoutCell.swapButton.isenabled = false
}
But Xcode doesn't allow you to add IBOutlets to repeating cells so I'm a bit stuck.
I'm fairly new to delegates (managed to get it working for displaying the table view) so if it has something simple to do with that sorry!
Add a property to your viewcontroller:
var swapButtonsDisabled : Bool = false
In your cellForRow do something like this:
cell.swapButton.isEnabled = !self.swapButtonsDisabled
When the start button is pressed, set swapButtonDisabled to true and reload the tableView.
1- As you connect
#IBOutlet weak var exerciseName: UILabel!
create outlet for every btn
#IBOutlet weak var btn1: UIButton!
2- Add a property to the model array in the VC to hold the state of every cell
3- When you click the main btn fire the delegate method with the btn's cell
4- In VC delegate handle method disable the btns and change the state of the array index path
5- Don't forget to check state in cellForRow
You are pretty close. First I suggest you to be more specific and have the data you need in cell and use access control:
class WorkoutCell : UITableViewCell {
var workoutSwappable: (workout: Workout, canBeSwapped: Bool)? {
didSet {
swapButton.isEnabled = workoutSwappable?.canBeSwapped == true
// TODO: do additional setup here
}
}
weak var delegate: WorkoutCellDelegate? // Needs to be weak or you will have memory leaks
#IBAction private func swapButtonPressed(_ sender: Any) {
if let workoutSwappable = workoutSwappable, workoutSwappable.canBeSwapped == true {
delegate?.workoutCell(self, didTapWorkoutSwap: workoutSwappable.workout)
}
}
#IBOutlet private var exerciseName: UILabel!
#IBOutlet private var repsNumber: UILabel!
#IBOutlet private var swapButton: UIButton!
}
Ok so now in cell for row at index path all you need is something like:
cell.workoutSwappable = (self.items[0], self.canSwap)
On delegate you now have:
func workoutCell(_ sender: WorkoutCell, didTapWorkoutSwap workout: workout) {
self.currentWorkout = workout
self.canSwap = false
self.initializeTimer()
tableView.reloadData() // This will now flush all the buttons
}
I have a view that contains labels, and have a button to change the values of the labels. Instead of changing the values one by one in the button, how can I reload the whole view to update the labels.
#IBOutlet weak var one: UILabel!
#IBOutlet weak var two: UILabel!
#IBOutlet weak var three: UILabel!
....
#IBOutlet weak var updateLabels: UIButton!{
//doing something to change the value of the labels
//then wanna reload the whole view
viewDidLoad()
}
I had called the viewDidLoad() method, but didn't work.
You should never call viewDidLoad yourself. It's a framework function that the OS calls, as an indication that your views are ready to be setup.
It would serve better if you separated your function
func updateLabels() {
one.text = "one"
two.text = "two"
three.text = "three"
}
and now you can call the updateLabels function when you want.
Why dont you put all labels on a method. and fire it when ever you need to reload.
override func viewDidLoad() {
super.viewDidLoad()
updateLabels()
}
func updateLabels() {
one.text = "one"
two.text = "two"
three.text = "three"
}
#IBAction func updateLabels(_ sender: Any) {
updateLabels()
}
Your method of updating your labels is incorrect. What you need to do is as follows:
Declare your labels like you did ensuring they are linked in Interface Builder:
//Declare The Labels
#IBOutlet var one: UILabel!
#IBOutlet var two: UILabel!
#IBOutlet var three: UILabel!
Then create an IBAction function which is triggered by a UIButton:
/// Set The Text Labels Text
#IBAction func updateLabelText(){
//Set Label Text
one.text = "one"
two.text = "two"
three.text = "three"
}
Of course remembering to link this to the UIButton instance in Interface Builder.
Hope this helps.
I've a tableView with a cell for each pokemon in the "pokedex". When I press a button in the Cell, I want to show a view with details about this creature.
What I do is that I have a global variable "currentPokemon", that I set to the requested pokemon when the button is pressed. Here's my code for the Cell :
class PokemonTableViewCell: UITableViewCell {
var pokemon: Pokemon!
#IBOutlet weak var pokemonImage: UIImageView!
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var releaseDateLabel: UILabel!
#IBAction func setPokemon(sender: UIButton) {
currentPokemon = self.pokemon
}
}
When I try to access the currentPokemon var in the details view, I get a fatal error because currentPokemon is nil. How could I get this code to be executed before the segue ?
You need to add store for selected Pokemon with singleton instance like:
struct Pokemon {
let name: String
}
class PokemonStore {
static let instance = PokemonStore()
var currectPokemon: Pokemon?
}
Later you can get saved pokemon in any place of your code with PokemonStore.instance.currectPokemon
If you are using segue, you should pass data to detail controller in this UIViewController method:
func performSegue(withIdentifier identifier: String, sender: Any?)
Is it possible to have an #IB Action function inside of viewDidLoad() ?
The action is a simple one - a Stepper that increases other label.text values accordingly. However, the values that the stepper needs to work with depend on the return content of a url - which are only known after the viewDidLoad() of course.
So I think I can't have the IBaction way up on top before the viewDidLoad(), and the error I get if I try to do my IB action inside of the viewDidLoad() is:
"Only instance methods can be declared ‘IBAction' ”
EDIT
Let me clarify myself, sorry for the confusion. I know I need an outlet to get the UIStepper values from. I have that:
#IBOutlet weak var stepper: UIStepper!
I then have an action also connected to same UIStepper that will increase/decrease value of a label's text (new_total) accordingly:
#IBOutlet weak var new_total: UILabel!
#IBAction func step_up_pass(sender: AnyObject) {
new_total.text = "\(Int(stepper.value))"
}
However, I want to start out with a value (todays_price) I'm getting back from a json request and use that as a starting point, to multiply it using the stepper and put the multiplied value into the label's text.
I have a struct in a separate file that defines my object so:
struct PassengerFromOtherBus {
var fname: String?
var lname: String?
var todays_price: Int?
init(json: NSDictionary) {
self.fname = json["fname"] as? String
self.lname = json["lname"] as? String
self.todays_price = json["todays_price"] as? Int
}
}
So later on in the view controller, inside of the viewDidLoad(), after connecting to the URL and then parsing it using NSJSONSerialization and a bunch of other code here (that I don't need to confuse you with) I finally have my value todays_price. So my question is, how do I get my action to use that value when it's only known inside of my viewDidLoad()? Xcode will not even let me connect the IBAction to anywhere inside the viewDidLoad function!
This is not done with an Action but with an Outlet. Connect the Stepper from IB as an Outlet to your ViewController. Then just set the values of the Stepper in ViewDidLoad.
I would never go directly from a UIStepper.value to UILabel.text.
Use an intermediary variable to store the value.
Do the same for the return from the JSON. By setting a didSet function on those variables you can update the UI when any of the values is updated.
class FirstViewController: UIViewController {
var todays_price: Int = 0 {
didSet { // didSet to trigger UI update
myLabel.text = "\(stepperValue * todays_price)"
}
}
var stepperValue : Int = 1 {
didSet { // didSet to trigger UI update
myLabel.text = "\(stepperValue * todays_price)"
}
}
#IBOutlet weak var myStepper: UIStepper!
#IBOutlet weak var myLabel: UILabel!
override func viewDidLoad() {
//
let returnValueFromJson = 10
todays_price = returnValueFromJson
}
#IBAction func stepperUpdate(sender: AnyObject) {
stepperValue = Int(myStepper.value)
}
}
Just add a variable to the top of your view controller to hold the value from your json request. Then in viewDidLoad you update that variable, and then you can use it to set your label and inside the IBAction (that doesn't have to be inside viewDidLoad).
So you would do something like this:
class WhateverViewController: UIViewController {
var todays_price: Int!
override func viewDidLoad() {
super.viewDidLoad()
todays_price = // The value you got from json goes here
new_total.text = "\(todays_price)"
}
#IBAction func step_up_pass(sender: AnyObject) {
new_total.text = "\(Int(stepper.value) * todays_price)"
}
}