This question already has answers here:
How to initialize properties that depend on each other
(4 answers)
Closed 3 years ago.
I'm trying to follow along CS 193P course and I'm currently on a Lecture 2. The lector made var game = Concentration(numberOfPairsOfCards: cardButtons.count / 2) and it worked fine for him, however, it doesn't work at all for me. Am I missing something? I can't pass anything there.
Here's my ViewController class:
class ViewController: UIViewController {
#IBOutlet weak var flipCountLabel: UILabel!
#IBOutlet var cardButtons: [UIButton]!
var game = Concentration(numberOfPairsOfCards: cardButtons.count / 2)
var emojiChoices = ["🎃", "👻", "🎃", "👻"]
var flipCount = 0 {
didSet {
flipCountLabel.text = "Flips: \(flipCount)"
}
}
//MARK: - IBActions
#IBAction func touchCard(_ sender: UIButton) {
flipCount += 1
if let cardNumber = cardButtons.index(of: sender) {
flipCard(withEmoji: emojiChoices[cardNumber], on: sender)
}
}
//MARK: - Methods
func flipCard(withEmoji emoji: String, on button: UIButton) {
if button.currentTitle == emoji {
button.setTitle("", for: .normal)
button.backgroundColor = #colorLiteral(red: 0.9372549057, green: 0.3490196168, blue: 0.1921568662, alpha: 1)
} else {
button.setTitle(emoji, for: .normal)
button.backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
}
}
}
And Concentration:
class Concentration {
var cards = [Card]()
func chooseCard(at index: Int) {
}
init(numberOfPairsOfCards: Int) {
for _ in 1...numberOfPairsOfCards {
let card = Card()
cards += [card, card]
}
//TODO: - Shuffle the cards
}
}
The error is
Cannot use instance member 'cardButtons' within property initializer;
property initializers run before 'self' is available
and auto completion doesn't work either
You need to make it a var
var game:Concentration!
then inside viewDidLoad
game = Concentration(numberOfPairsOfCards: cardButtons.count / 2)
OR make it a lazy var
lazy var game:Concentration = {
return Concentration(numberOfPairsOfCards:self.cardButtons.count / 2)
}()
Related
I struggle with learning MVVM architecture. My problem is that I can't send data to the next VC.
App idea: I have 2 view controllers. In first VC user sets own parameteres (height and weight with UISlider). Later app presents second VC where user has got information about BMI.
MODEL
struct BMI {
let value: Float
let advice: String
let color: UIColor
let diagnosis: String
}
VIEW MODEL
protocol BmiViewControllerDelegate: class {
func getCalculatedBMI(newBmi: BMI)
}
protocol BmiViewModelDelegate: class {
func sendValue(height: Float?, weight: Float?)
}
class BmiViewModel: BmiViewModelDelegate {
var bmiModel = BmiCalculator()
var bmi: BMI
weak var delegateVC: BmiViewControllerDelegate?
func sendValue(height: Float?, weight: Float?) {
guard let height = height, let weight = weight else { return }
calculateBmi(height: height, weight: weight)
}
func calculateBmi(height: Float, weight: Float) {
let bmiValue = weight / pow(height, 2)
if bmiValue < 18.5 {
bmi = BMI(value: bmiValue, advice: "You should eat more calories", color: .red, diagnosis: "Underweight")
delegateVC?.getCalculatedBMI(newBmi: bmi!)
} else if bmiValue < 24.9 {
bmi = BMI(value: bmiValue, advice: "Your weight is great! Keep it up!", color: .green, diagnosis: "")
delegateVC?.getCalculatedBMI(newBmi: bmi!)
} else {
bmi = BMI(value: bmiValue, advice: "You should eat less calories", color: .red, diagnosis: "Overweight")
delegateVC?.getCalculatedBMI(newBmi: bmi!)
}
}
}
VIEW CONTROLLER
class BMIViewController: UIViewController {
var bmiViewModel = BmiViewModel()
#IBOutlet weak var heightLabel: UILabel!
#IBOutlet weak var heightSlider: UISlider!
#IBOutlet weak var weightLabel: UILabel!
#IBOutlet weak var weightSlider: UISlider!
#IBOutlet weak var calculateButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
bmiViewModel.delegateVC = self
heightSlider.value = 1.5
weightSlider.value = 80
}
#IBAction func heightSliderChanged(_ sender: UISlider) {
let height = String(format: "%.2f", sender.value)
heightLabel.text = "\(height)m"
}
#IBAction func weightSliderChanged(_ sender: UISlider) {
let weight = String(format: "%.0f", sender.value)
weightLabel.text = "\(weight)kg"
}
#IBAction func calculateButtonTapped(_ sender: UIButton) {
let height = heightSlider.value
let weight = weightSlider.value
bmiViewModel.sendValue(height: height, weight: weight)
}
}
extension BMIViewController: BmiViewControllerDelegate {
func getCalculatedBMI(newBmi: BMI) {
let bmiResult = BMIResultViewController()
bmiResult.bmiValue = String(newBmi.value)
bmiResult.advice = newBmi.advice
bmiResult.diagnosis = newBmi.diagnosis
bmiResult.color = newBmi.color
}
I've tried to print values in getCalculatedBMI and these values exists, so why when I open BMIResultViewController values are empty.
And I have one additional question: if force unwrapped bmi values in "delegateVC?.getCalculatedBMI(newBmi: bmi!)" isn't bad approach?
This code
func getCalculatedBMI(newBmi: BMI) {
let bmiResult = BMIResultViewController()
bmiResult.bmiValue = String(newBmi.value)
bmiResult.advice = newBmi.advice
bmiResult.diagnosis = newBmi.diagnosis
bmiResult.color = newBmi.color
}
Doesn't do what you want it to.
First, it allocates a new BMIResultViewController, but this view controller isn't shown in any way. It will just be thrown away when this function exits.
But this is academic because getCalculatedBMI isn't even called. You have used a delegation pattern, but the instance of BMIResultViewController that is shown by the segue can't invoke it since it doesn't have a model instance with the delegateVC property set.
First, let's fix your model and view model objects
class BMIModel {
var weight: Float = 0 {
didSet: {
calculateBMI()
}
}
var height: Float = 0 {
didSet: {
calculateBMI()
}
}
var bmi: BMI
init() {
self.bmi = BMI(value: 0, advice: "You should eat more calories", color: .red, diagnosis: "Underweight")
}
private func calculateBMI() {
let bmiValue = self.weight / pow(self.height, 2)
if bmiValue < 18.5 {
self.bmi = BMI(value: bmiValue, advice: "You should eat more calories", color: .red, diagnosis: "Underweight")
} else if bmiValue < 24.9 {
self.bmi = BMI(value: bmiValue, advice: "Your weight is great! Keep it up!", color: .green, diagnosis: "")
} else {
self.bmi = BMI(value: bmiValue, advice: "You should eat less calories", color: .red, diagnosis: "Overweight")
}
}
}
struct BmiViewModel {
let bmiModel: BMIModel
func setValues(weight: Float, height: Float) {
self.bmiModel.weight = weight
self.bmiModel.heihght = height
}
}
struct BmiResultViewModel {
let bmiModel: BMIModel
var weight: Float {
return bmiModel.weight
}
var height: Float {
return bmiModel.height
}
var bmiResult: BMI {
return bmiModel.bmi
}
}
You have a final problem in that you have a segue triggered from your button and you are also using an #IBAction from that button to calculate the bmi. It isn't defined in which order these things will happen. In many cases the segue will trigger before the tap handler executes.
You can fix this by changing the storyboard segue to one linked to the view controller that you perform by identifier or you can perform the calculation during the segue.
The former is probably the correct approach:
Remove the segue between the button and the destination
Create a segue between the view controller object itself and the destination
Give the segue an identifier, say showResults
Fix your view model so that it updates the model.
Now you can invoke the segue programmatically.
You need to implement prepare(for:sender) in BMIViewController in order to set properties on the destination view controller.
I wouldn't bother with the delegate pattern. Just set the destination view model.
class BMIViewController: UIViewController {
let bmiViewModel = BmiViewModel(model: BMIModel())
#IBAction func calculateButtonTapped(_ sender: UIButton) {
let height = heightSlider.value
let weight = weightSlider.value
self.bmiViewModel.setValues(weight: weight, height: height)
self.perform(segueWithIdentifier:"showResult", sender:self}
}
func prepare(for segue:UIStoryboardSegue, sender: any?) {
if let destVC = segue.destinationViewController as? BMIResultViewController {
destVC.bmiViewModel = BmiResultViewModel(bmiModel: self.bmiViewModel.bmiModel)
}
}
}
This is just my opinion, but UIKit is inherently MVC. Retrofitting MVVM really only makes sense if you introduce a binding framework (which is what you were trying to do with the delegate pattern).
If you want to learn MVVM, use SwiftUI.
I have a custom view CustomSegmentedControl in my app which have its protocol with changed index function as below,
protocol SegmentControllerDelegate:class {
func indexChanged(index : Int)
}
class CustomSegmentedControl: UIView {
weak var delegate : SegmentControllerDelegate?
#objc func buttonAction(sender:UIButton) {
for (buttonIndex, btn) in buttons.enumerated() {
btn.setTitleColor(textColor, for: .normal)
if btn == sender {
let selectorPosition = frame.width/CGFloat(buttonTitles.count) * CGFloat(buttonIndex)
selectedIndex = buttonIndex
//delegate?.changeToIndex(index: buttonIndex)
if(delegate != nil){
delegate?.indexChanged(index: buttonIndex)
}else{
print(buttonIndex)
}
UIView.animate(withDuration: 0.3) {
self.selectorView.frame.origin.x = selectorPosition
}
btn.setTitleColor(selectorTextColor, for: .normal)
}
}
}
I added a view to my ViewController Mail and set the class to my custom view and make outlet for it and use the protocol as well like below,
import UIKit
class Mail: UIViewController, SegmentControllerDelegate {
func indexChanged(index: Int) {
print(index)
switch index {
case 0:
container.bringSubviewToFront(inbox)
break
case 1:
container.bringSubviewToFront(outbox)
break
default:
break
}
}
#IBOutlet weak var segment: CustomSegmentedControl!{
didSet{
segment.setButtonTitles(buttonTitles: ["First","Second"])
segment.selectorTextColor = .orange
segment.selectorViewColor = .orange
}
}
#IBOutlet weak var container: UIView!
var inbox : UIView!
var outbox : UIView!
override func viewDidLoad() {
super.viewDidLoad()
segment.delegate = self
inbox = Inbox().view
outbox = GPS().view
container.addSubview(inbox)
container.addSubview(outbox)
}
But the protocol is always nil and the function never be called !?, What Im missing here ?
Any help will be much appreciated
I need help I have these ColorSlider App that change the color of Square with 3 sliders RGB .I like to create a Tableview where i can store presets of color and retrieve from preset name e.g. Ocean Blue,
Emerald Green, Red Ferrary etc. I create The Tableview with the push of the button but I am stuck with the rest of implementation. Please I need help.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var redSlider: UISlider!
#IBOutlet weak var greenSlider: UISlider!
#IBOutlet weak var blueSlider: UISlider!
#IBOutlet weak var displaylbl: UILabel!
#IBOutlet weak var displayView: UIView!
var color:Color!
override func viewDidLoad() {
super.viewDidLoad()
color = Color(red: redSlider.value, green: greenSlider.value, blue: blueSlider.value)
displaylbl.text = color.getString()
displayView.backgroundColor = color.getColor()
}
#IBAction func sliderChanged(_ sender: UISlider) {
if (sender.tag == 1){
color.setRed(red: sender.value)
displaylbl.text = color.getString()
displayView.backgroundColor = color.getColor()
}
else if (sender.tag == 2) {
color.setGreen(green: sender.value)
displaylbl.text = color.getString()
displayView.backgroundColor = color.getColor()
}
else if (sender.tag == 3) {
color.setBlue(blue: sender.value)
displaylbl.text = color.getString()
displayView.backgroundColor = color.getColor()
}
}
}
import UIKit
class Color {
private var red:CGFloat
private var green:CGFloat
private var blue:CGFloat
init(red:Float, green:Float, blue:Float) {
self.red = CGFloat(red)
self.green = CGFloat(green)
self.blue = CGFloat(blue)
}
func setRed(red:Float) {
self.red = CGFloat(red)
}
func setGreen(green:Float) {
self.green = CGFloat(green)
}
func setBlue(blue:Float) {
self.blue = CGFloat(blue)
}
func getColor() -> UIColor {
let color = UIColor(red: red/255, green: green/255, blue: blue/255, alpha: 1.0)
return color
}
func getString() -> String {
let string = "Red: \(Int(round(red)))\nGreen: \(Int(round(green)))\nBlue: \(Int(round(blue)))"
return string
}
}
First you need to get the RGB color from slider and show in box, if you have done this part then now you need to do is whenever you are showing color in box you just make a dictionary with keys like :
let dict = [ colorName : "whatever you want",
colorCode : "Which you get from slider"
]
Add it in array too like:
arrayColor.append(dic)
Populate your tableview from arrayColor array, and in didselectatindexpath you need to get colorCode atIndexPath.row and set in box.
Hope this will help you. For any other query comment below.
I am following the standford ios class for Fall 2017. As the professor goes through the demo, I follow by inputting and running like he does. He shows how using lazy with a var allows one to use the UIButton count for variable initialization. When I add the lazy keyword, the error doesn't go away. Thinking it was maybe an xcode update related problem, I downloaded someone else's version of the project and that project doesn't have the issue. The code is below, any thoughts? :/
class ViewController: UIViewController {
lazy var game = ConcentrationModel(numberOfPairsOfCards: (cardButtons.count + 1) / 2)
//Could have had var flipCount: Int = 0
//But it is inferred
var flipCount = 0 {
didSet {
flipCountLabel.text = "Flips: \(flipCount)"
}
}
var emojiChoices = ["🎃","👻","👿","🎃","👻","👿"]
#IBOutlet var cardButtons: [UIButton]!
#IBOutlet weak var flipCountLabel: UILabel!
#IBAction func touchCard(_ sender: UIButton) {
flipCount += 1
if let cardNumber = cardButtons.index(of: sender) {
flipCard(withEmoji: emojiChoices[cardNumber], on: sender)
print("cardNumber = \(cardNumber)")
} else {
print("chosen card was not in cardButtons")
}
print("agh!!! a ghost")
}
func flipCard(withEmoji emoji: String, on button: UIButton) {
if button.currentTitle == emoji {
button.setTitle("", for: UIControlState.normal)
button.backgroundColor = #colorLiteral(red: 1, green: 0.5763723254, blue: 0, alpha: 1)
} else {
button.setTitle(emoji, for: UIControlState.normal)
button.backgroundColor = #colorLiteral(red: 0.9999960065, green: 1, blue: 1, alpha: 1)
}
}
}
To initialize a lazy property which depends on the value of another property you have to use the closure syntax
lazy var game : ConcentrationModel = {
return ConcentrationModel(numberOfPairsOfCards: (self.cardButtons.count + 1) / 2)
}()
This question already has answers here:
How to generate a random number in Swift?
(26 answers)
Closed 6 years ago.
I have to text fields on a differnet scene.One text field has a smaller number and another one has a bigger number.I need to take those numbers and generate a random number using the smallest number and the biggest number.Then I needed to make a multiplication problem out of it.Example:User enters in 4 and 8.I need to make two random numbers that is more than 4 and less than 8.Here is my code:
//
// CustomModeWithTimer.swift
// BetaVersion0.1
//
// Created by James Ikeler on 5/26/16.
// Copyright © 2016 James Ikeler. All rights reserved.
//
import UIKit
import Foundation
var CustomNumber1 = UInt32(CustomInputForGame)
var CustomNumber2 = UInt32(CustomInput2ForGame)
var Random = arc4random_uniform(CustomNumber2) + CustomNumber1
var Random2 = arc4random_uniform(CustomNumber2) + CustomNumber1
var UnConvertInt = 0
var scorecustom = 0
var TimerCustom = NSTimer()
var CountDownCustom = 10
var GameOverCustom = UIAlertView()
var AnswerCustom = Int(CustomNumber1 * CustomNumber2)
class CustomModeWithTimer: UIViewController {
#IBOutlet weak var CustomInputForGame3: UITextField!
#IBOutlet weak var QuestionForCustomGame: UILabel!
#IBOutlet weak var ScoreLabelForCustom: UILabel!
#IBOutlet weak var TimerCustomLabel: UILabel!
#IBOutlet weak var RightOrWrongCustom: UILabel!
#IBOutlet weak var CustomImgForGame: UIImageView!
func IfAnswerWrong() {
print("TEST\(AnswerCustom)")
CustomInputForGame3.text = ""
CustomImgForGame.image = UIImage(named: "Label")
CustomImgForGame.hidden = false
RightOrWrongCustom.hidden = false
RightOrWrongCustom.text = "Wrong!"
RightOrWrongCustom.textColor = UIColor(red: 225, green: 0, blue: 0, alpha: 1)
}
func IfAnswerRight() {
CountDownCustom = 10
scorecustom += 1
ScoreLabelForCustom.text = "Score: \(scorecustom)"
Random = arc4random_uniform(CustomNumber2) + CustomNumber1
Random2 = arc4random_uniform(CustomNumber2) + CustomNumber1
QuestionForCustomGame.text = "\(Random) x \(Random2)"
AnswerCustom = Int(Random * Random2)
CustomImgForGame.image = UIImage(named: "Label")
RightOrWrongCustom.hidden = false
CustomImgForGame.hidden = false
RightOrWrongCustom.text = "Right!"
RightOrWrongCustom.textColor = UIColor(red: 0, green: 225, blue: 0, alpha: 1)
CustomInputForGame3.text = ""
}
#IBAction func ConfirmAnswerCustom(sender: AnyObject) {
TimerCustom.invalidate()
TimerCustom = NSTimer.scheduledTimerWithTimeInterval(0.8, target: self,selector: Selector("UpdateClockCustom"), userInfo: nil, repeats: true)
let InputAnswerCustom = Int(CustomInputForGame3.text!)
if InputAnswerCustom == AnswerCustom {
IfAnswerRight();
} else {
IfAnswerWrong();
}
}
func UpdateClockCustom() {
TimerCustomLabel.text = ("Time: \(CountDownCustom--)")
if CountDownCustom == -2 {
TimerCustom.invalidate()
GameOverCustom.title = "Game Over!"
GameOverCustom.message = "Nice job you got a score of \(scorecustom).The answer to the question you missed was \(AnswerCustom)!"
GameOverCustom.addButtonWithTitle("Play Again!")
GameOverCustom.show()
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
Define a randBetween function:
func randBetween(lower: Int, _ upper: Int) -> Int {
return Int(arc4random_uniform(UInt32(upper - lower - 1))) + lower + 1
}
This returns a random number x where lower < x < upper. It doesn't check that lower + 1 < upper which you can decide how to handle yourself.