UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Day 19 Challenge - Dealing with bad input

Forums > 100 Days of SwiftUI

Hi so my conversion app is working as intended (almost!) I would like to pop an alert when a valid Double is not provided as well as setting the default value to 0. I have almost achieved this but I believe I am changing my showingAlert variable to true in the wrong place. I get the warning:

Modifying state during view update, this will cause undefined behaviour

Can anyone help?

//
//  ContentView.swift
//  Converter
//
//  Created by Lee Casey on 09/08/2020.
//  Copyright © 2020 Lee Casey. All rights reserved.
//
//  C -> F = (0°C × 9/5) + 32 = 32°F
//  F -> C = (0°F − 32) × 5/9
//  C -> K = 0°C + 273.15 = 273.15K

import SwiftUI

struct ContentView: View {
    @State private var source = ""
    @State private var sourceSelection = 0
    @State private var resultSelection = 0
    @State private var showingAlert = false

    let units = ["Celcius", "Farenheit", "Kelvins"]

    //work all calculations from C
    func calcBase() -> Double {
        //let x = Double(source) ?? 0.0
        var x: Double

        if let unwrapped = Double(source) {
            x = unwrapped
        } else {
            x = 0.0
            showingAlert = true
        }

        if (sourceSelection == 1) {
           return (x - 32) * 5/9
        }

        else if (sourceSelection == 2) {
            return x - 273.15
        }

        return x
    }

    func calculate() -> Double {
        if (resultSelection == 0) {
            return Double(calcBase())
        }
        else if (resultSelection == 1) {
            return (Double(calcBase()) * 9/5) + 32
        }
        else {
            return (Double(calcBase()) + 273.15)
        }
    }

    var body: some View {
        NavigationView {
            Form {
                Section(header: Text("Please enter the amount you want to convert")) {
                    TextField("Enter here", text: $source)
                    .keyboardType(.decimalPad)
                    Picker("Unit", selection: $sourceSelection) {
                        ForEach(0 ..< units.count) {
                            Text("\(self.units[$0])")
                        }
                    }
                    .pickerStyle(SegmentedPickerStyle())
                    .alert(isPresented: $showingAlert) {
                           Alert(title: Text("ERROR"), message: Text("Please enter a valid number"), dismissButton: .default(Text("OK")))
                       }
                }
                Section(header: Text("Results")) {
                    Text("\(calculate(), specifier: "%.2f")")
                    Picker("Unit", selection: $resultSelection) {
                        ForEach(0 ..< units.count) {
                            Text("\(self.units[$0])")
                        }
                    }
                    .pickerStyle(SegmentedPickerStyle())
                }
            }
            .navigationBarTitle("Converter")
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

3      

Your call to calculate() changes the value of showingAlert, but calculate() is being called inside body{} so SwiftUI can't determine how to render the View reliably.

showingAlert needs to be set outside the View itself, in a modifier of some sort or perhaps a button. Hopefully someone more experienced can offer a specific solution!

3      

hi,

i'd recommend that you not try to pop up an Alert any time there's an illegal input in a live edit/convert like this -- it will be pretty annoying, and it just won't happen that often if you have the decimal keypad on screen. (by the way, entering a negative temperature is a problem on the decimal pad.)

instead, just replace the computation of the output converstion string so that it shows there's an error. just replace Text("\(calculate(), specifier: "%.2f")") with a basic function call like this:

Text(conversionResult())

the conversionResult could look something like this:

    func conversionResult() -> String {
        guard source.count > 0 else {
            return "0.0"
        }
        guard var inputValue = Double(source) else {
            return "Input Error"
        }
        // convert to C
        if (sourceSelection == 1) {
            inputValue = (inputValue - 32) * 5/9
        } else if (sourceSelection == 2) {
            inputValue -= 273.15
        }
        // convert to desired output base
        var convertedValue: Double
        if (resultSelection == 0) {
            convertedValue = inputValue
        }
        else if (resultSelection == 1) {
            convertedValue = (inputValue * 9/5) + 32
        }
        else {
            convertedValue = inputValue + 273.15
        }
        return String(format:"%.2f", convertedValue)
    }

that should do it for you.

a comment to consider: the logic of conversion could be simplified if you took a look at Foundation's support for Measurement. Paul has some information on this.

hope that helps,

DMG

3      

Thank you everyone. Much appreciated.

3      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.