BLACK FRIDAY: Save 50% on all my Swift books and bundles! >>

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      

Save 50% in my WWDC sale.

SAVE 50% All our books and bundles are half price for Black Friday, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

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.