I'm currently working on a unit conversion app, and ran into a problem that I'm still trying to figure how to solve it. The app is supposed to convert one unit to a different unit, and supposed to convert and display new value in the text view every time a unit is converted to a different unit.(meters to kilometers, miles to yards, etc.). I created a computed property called result which handles the conversion, but when I run the code in the simulator and toggle between the units, no changes are displayed to show the units converted successfully. I tried using a guard statement before the calculations since I figured Value is 0 after converting inputNumber to an integer, but that doesn't seem to work. I'm not sure where I went wrong but any help with this would be greatly appreciated. I have attached the code and simulator below.
Thanks.
PS. I have learned how to and can build this app using the measurement functionality provided by Apple, I'm trying to learn how to build it in a different way by basically using math calculations only.
struct ContentView: View {
#State private var inputNumber = ""
#State private var inputUnitValue = 2
#State private var outputUnitValue = 2
#State private var outputValue = ""
let inputUnits = ["Feet", "Meters", "Kilometers", "Yard", "Miles"]
let outputUnits = ["Feet", "Meters", "Kilometers", "Yard", "Miles"]
var result: Double {
var outputType = ""
var inputType = ""
let value = Double(inputNumber) ?? 0
var output = Double(outputValue) ?? 0
var input = Double(inputNumber) ?? 0
guard value > 0 else {
return 0
}
//Converts input value to input unit
switch inputUnits[inputUnitValue] {
case "meters":
input = value * 3.28
case "kilometers":
input = value * 3280.84
case "yards":
input = value * 3
case "miles":
input = value * 5280
case "feet":
input = value * 1
default:
input = value * 3.28
}
//Converts input unit to output unit
switch outputUnits[outputUnitValue] {
case "meters":
output = value / 3.28
case "kilometers":
output = value / 3280.84
case "yards":
output = value / 3
case "miles":
output = value / 5280
case "feet":
output = value / 1
default:
output = value / 3.28
}
return output
}
var body: some View {
NavigationView {
Form {
Section {
TextField("Value", text: $inputNumber)
.keyboardType(.numberPad)
}
Section(header: Text("Input Units")) {
Picker("Input Units", selection: $inputUnitValue) {
ForEach(0 ..< inputUnits.count) {
Text("\(self.inputUnits[$0])")
}
}
.pickerStyle(SegmentedPickerStyle())
}
Section(header: Text("Output Units")) {
Picker("Input Units", selection: $outputUnitValue) {
ForEach(0 ..< outputUnits.count) {
Text("\(self.outputUnits[$0])")
}
}
.pickerStyle(SegmentedPickerStyle())
}
Section (header: Text("output")){
Text("\(result, specifier: "%.2f")")
}
}
.navigationBarTitle("Unit Conversion")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
switch is case sensitive. Your inputUnits and outputUnits are capitalized...
let inputUnits = ["Feet", "Meters", "Kilometers", "Yard", "Miles"]
... while your switch conditions are lowercased.
switch inputUnits[inputUnitValue] {
case "meters": /// lowercased
...
}
So, your switch statement always falls through to the default.
To fix, just add .lowercased():
switch inputUnits[inputUnitValue].lowercased() {
switch outputUnits[outputUnitValue].lowercased() {
One more thing: I assume the purpose of your switches are to:
Convert value to a standard feet unit
Convert that feet to the output unit
In this case, inside the second switch, you need to perform calculations using your input variable (which is the input converted to feet) - not value.
To make things less confusing, I've renamed input to inputAsFeet.
var result: Double {
let value = Double(inputNumber) ?? 0
var inputAsFeet = Double(0)
var output = Double(0)
guard value > 0 else {
return 0
}
switch inputUnits[inputUnitValue].lowercased() {
case "meters":
inputAsFeet = value * 3.28
case "kilometers":
inputAsFeet = value * 3280.84
case "yards":
inputAsFeet = value * 3
case "miles":
inputAsFeet = value * 5280
case "feet":
inputAsFeet = value * 1
default:
inputAsFeet = value * 3.28
}
switch outputUnits[outputUnitValue].lowercased() {
case "meters":
output = inputAsFeet / 3.28
case "kilometers":
output = inputAsFeet / 3280.84
case "yards":
output = inputAsFeet / 3
case "miles":
output = inputAsFeet / 5280
case "feet":
output = inputAsFeet / 1
default:
output = inputAsFeet / 3.28
}
return output
}
Result:
Related
I am a novice at programming and exploring SwiftUI. I've been tackling a challenge for too long, and hoping that someone can guide me to the right direction!
I want a list of interlinked sliders (as in Interlinked Multiple Sliders in SwiftUI), but with the number of sliders that change dynamically, depending on actions taken by a user.
For example, a user can choose various items, and later on adjust the percentage variable with sliders (and where these percentages are interdependent as in the linked example).
class Items: ObservableObject {
#Published var components = [ItemComponent]()
func add(component: itemComponent){
components.append(component)
}
}
struct ItemComponent: Hashable, Equatable, Identifiable {
var id = UUID().uuidString
var name: String = ""
var percentage: Double
}
Conceptually, it seems I need to do two things to adapt the linked code:
generate an array of Binding with the number of elements equal to Items.Component.EndIndex and
assign each Binding to the percentage of each ItemComponent.
I am fumbling on both. For 1., I can easily manually create any number of variables, e.g.
#State var value1 = 100
#State var value2 = 100
#State var value3 = 100
let allBindings = [$value1, $value2, $value3]
but how do I generate them automatically?
For 2., I can use ForEach() to call the components, or Index, but not both together:
ForEach(Items.components){ component in
Text("\(component.name)")
Text("\(component.percentage)")
}
ForEach(Items.components.indices){ i in
synchronizedSlider(from: allBindings, index: i+1)
}
In broken code, what I want is something like:
ForEach(Items.component){component in
HStack{
Text("component.name")
Spacer()
synchronizedSlider(from: allBindings[$component.percentage], index: component.indexPosition)
}
where allBindings[$component.percentage] is a binding array comprised of each itemComponent's percentage, and the index is an itemComponent's index.
I am happy to share more code if relevant. Any help would be greatly appreciated!
To adapt the existing code you linked, if you're going to have a dynamic number of sliders, you'll definitely want your #State to be an array, rather than individual #State variables, which would have to be hard coded.
Once you have that, there are some minor syntax issues changing the synchronizedBinding functions to accept Binding<[ItemComponent]> rather than [Binding<Double>], but they are pretty minor. Luckily, the existing code is pretty robust outside of the initial hard-coded states, so there isn't any additional math to do with the calculations.
I'm using ItemComponent rather than just Double because your sample code included it and having a model with a unique id makes the ForEach code I'm using for the sliders easier to deal with, since it expects uniquely-identifiable items.
struct ItemComponent: Hashable, Equatable, Identifiable {
var id = UUID().uuidString
var name: String = ""
var percentage: Double
}
struct Sliders: View {
#State var values : [ItemComponent] = [.init(name: "First", percentage: 100.0),.init(name: "Second", percentage: 0.0),.init(name: "Third", percentage: 0.0),.init(name:"Fourth", percentage: 0.0),]
var body: some View {
VStack {
Button(action: {
// Manually setting the values does not change the values such
// that they sum to 100. Use separate algorithm for this
self.values[0].percentage = 40
self.values[1].percentage = 60
}) {
Text("Test")
}
Button(action: {
self.values.append(ItemComponent(percentage: 0.0))
}) {
Text("Add slider")
}
Divider()
ScrollView {
ForEach(Array(values.enumerated()),id: \.1.id) { (index,value) in
Text(value.name)
Text("\(value.percentage)")
synchronizedSlider(from: $values, index: index)
}
}
}.padding()
}
func synchronizedSlider(from bindings: Binding<[ItemComponent]>, index: Int) -> some View {
return Slider(value: synchronizedBinding(from: bindings, index: index),
in: 0...100)
}
func synchronizedBinding(from bindings: Binding<[ItemComponent]>, index: Int) -> Binding<Double> {
return Binding(get: {
return bindings[index].wrappedValue.percentage
}, set: { newValue in
let sum = bindings.wrappedValue.indices.lazy.filter{ $0 != index }.map{ bindings[$0].wrappedValue.percentage }.reduce(0.0, +)
// Use the 'sum' below if you initially provide values which sum to 100
// and if you do not set the state in code (e.g. click the button)
//let sum = 100.0 - bindings[index].wrappedValue
let remaining = 100.0 - newValue
if sum != 0.0 {
for i in bindings.wrappedValue.indices {
if i != index {
bindings.wrappedValue[i].percentage = bindings.wrappedValue[i].percentage * remaining / sum
}
}
} else {
// handle 0 sum
let newOtherValue = remaining / Double(bindings.wrappedValue.count - 1)
for i in bindings.wrappedValue.indices {
if i != index {
bindings[i].wrappedValue.percentage = newOtherValue
}
}
}
bindings[index].wrappedValue.percentage = newValue
})
}
}
Hi I'm stuck trying to solve this:
class Classy, to represent how classy someone or something is. "Classy". If you add fancy-looking items, "classiness" increases!
Create a method, addItem() in Classy that takes a string as input, adds it to the "items" array and updates the classiness total.
Add another method, getClassiness() that returns the "classiness" value based on the items.
The following items have classiness points associated with them:
"tophat" = 2
"bowtie" = 4
"monocle" = 5
Everything else has 0 points.
The sum is not performing correctly.
The first problem is when it falls in te default case, everything is 0, I've tried in the default with:
default:
self.classiness += 0
and I got 2 for every case
I've tried to sum the total inside in each case, and return the total but got the same result.
This is my last version
class Classy {
var items: [String]
var classiness: Int
init() {
self.items = []
self.classiness = 0
}
func addItem(_ item: String) {
var total = 0
self.items.append(item)
total += classiness
}
func getClassiness() -> Int {
switch items {
case ["tophat"]:
self.classiness = 2
case ["bowtie"]:
self.classiness = 4
case ["monocle"]:
self.classiness = 5
default:
self.classiness = 0
}
return self.classiness
}
}
let me = Classy()
print(me.getClassiness())
me.addItem("tophat")
print(me.getClassiness())
me.addItem("bowtie")
me.addItem("jacket")
me.addItem("monocle")
print(me.getClassiness()) //This would be 11
Your switch case need update, it need to loop and the case is String not Array
func getClassiness() -> Int {
var total = 0
for item in items{
switch item {
case "tophat":
total += 2
case "bowtie":
total += 4
case "monocle":
total += 5
default:
total +=0
}
}
self.classiness = total
return self.classiness
}
Hi so I have a class Calculations with a series of functions one of these is keplerianElementsToEcef. In my view controller I hard code the values for the parameters and then call the function. However later on in a seperate class I have a bool isInRange. If my spacecraft is out of cellular range, I return false and a string as well. I also want to then iterate through the keplerianElementsToEcef function, each time increasing the timeOfCalculation parameter by two minutes until at some point in time in the future the satellite is in range.
I've tried to simply call the function but increase the value used initially as the time, current time, by two minutes. The other variables rangeMeanMotion etc, are the same as those hardcoded in the view controller
var isInRange: Bool
var rangeString: String
if distance < range {
isInRange = true
rangeString = "In Range"
} else {
isInRange = false
rangeString = "Not In Range"
while isInRange == false {
var dateString = dateFormatter.date(from: calculationTime!)!
var updatedDate = dateString.addingTimeInterval(TimeInterval(5.0 * 60.0))
var updateDateAsString = dateFormatter.string(from: updatedDate)
Calculations.shared.keplerianElementsToECEF(meanMotion: rangeMeanMotion, eccentricity: rangeEccentricity, Inclination: rangeInclination, LongitudeAscendingNode: rangeLongitudeAscendingNode, argumentPerigee: rangeArgumentPerigee, M0: rangeM0, epoch: rangeEpoch, date: updateDateAsString) {
}
}
}
In the function parameters under date: updateDateAsString I get the following error: Extra argument 'date' in call
var timeOfCalculation : TimeInterval = 0
func doItUntilSpacecraftIsInRange(){
repeat {
timeOfCalculation += TimeInterval(2.0 * 60.0)
Calculations.shared.keplerianElementsToECEF(meanMotion: rangeMeanMotion, eccentricity: rangeEccentricity, Inclination: rangeInclination, LongitudeAscendingNode: rangeLongitudeAscendingNode, argumentPerigee: rangeArgumentPerigee, M0: rangeM0, epoch: rangeEpoch, date: updateDateAsString)
} while spacecraft.isInRange == false
}
doItUntilSpacecraftIsInRange()
I solved this issue. I made the statement iterate during a certain time period (1 day) and my code looks like this:
else {
isInRange = false
rangeString = "Not In Range"
print(calculationTime)
if let calcTime = calculationTime {
let parsedDate = dateFormatter.date(from: calcTime) ?? Date()
for interval in stride(from: 0, to: 1440, by: 2) {
var updatedDate = parsedDate.addingTimeInterval(TimeInterval(interval * 60))
var updateDateAsString = dateFormatter.string(from: updatedDate)
Calculations.shared.keplerianElementsToECEF(meanMotion: rangeMeanMotion, eccentricity: rangeEccentricity, Inclination: rangeInclination, LongitudeAscendingNode: rangeLongitudeAscendingNode, argumentPerigee: rangeArgumentPerigee, M0: rangeM0, epoch: rangeEpoch, date: updateDateAsString)
let xDistance = ecefX - wgs84X
let yDistance = ecefY - wgs84Y
let zDistance = ecefZ - wgs84Z
let iteratedDistance = sqrt(xDistance*xDistance + yDistance*yDistance + zDistance*zDistance)
if iteratedDistance < 7000 {
nextVisible = updateDateAsString
break
}
}
}
}
I was wondering what the best approach would be to separate multiple items from a string in swift. I'm hoping to separate a unit and an amount from a string and then use those values to create an object of my ingredient class.
For example:
var string = "4 cups sugar"
I would need to grab the 4 (amount) and convert it to an int and then grab the unit (cups)
var amount = 4
var unit = "cups"
Another example:
"3 large eggs"
In this case I would want to pull out the 3 (amount) and the unit would be empty.
var amount = 3
var unit = ""
Then I would create my object using the unit and amount values.
I'm still a novice at swift, and more-so with string manipulation so I'm not entirely sure how to approach this, any help in the right direction would be great.
I am working with an ingredient class that is structured as:
class IngredientModel {
var amount = 0
var unit = ""
init(amount : Int, unit : String) {
self.amount = amount
self.unit = unit
}
In SWIFT you can do this early with optionals & generics. I can show you how? with your example..
Your Ingredient class
class IngredientModel {
var amount = 0
var unit = ""
let units = ["cups","spoon"] //Add your required units
init(string: String) {
let array = string.components(separatedBy: " ")
if let _amount = array.ref(0) {
self.amount = Int(_amount) ?? 0
}
if let _unit = array.ref(1),
units.contains(_unit){
self.unit = _unit
}
}
}
An Array Extension to prevent index out of range
extension Array {
func ref(_ i:Int) -> Element? {
return 0 <= i && i < count ? self[i] : nil
}
}
If I'm reading the apple documentation right, you should use the components(separatedBy) function. The parameter should be one space in quotes like so:
let array = yourString.components(separatedBy: " ")
I need to implement an algorithm to check if an input is valid by calculating a modulo of a String.
The code in Kotlin:
private val facteurs = arrayOf(7, 3, 1)
private fun modulo(s: String): Int {
var result = 0
var i = -1
var idx = 0
for (c in s.toUpperCase()) {
val value:Int
if (c == '<') {
value = 0
} else if (c in "0123456789") {
value = c - '0'
} else if (c in "ABCDEFGHIJKLMNOPQRSTUVWXYZ") {
value = c.toInt() - 55
} else {
throw IllegalArgumentException("Unexpected character: $c at position $idx")
}
i += 1
result += value * facteurs[i % 3]
idx += 1
}
return result % 10
}
This implies doing math operations on the characters.
Is there an elegant way to do this in Swift 3 and 4?
I tried some cumbersome constructs like this :
value = Int(c.unicodeScalars) - Int("0".first!.unicodeScalars)
But it does not even compile.
I'm currently using Swift 4 with XCode9, but Swift3 answer is welcome too.
You can enumerate the unicodeScalars view of a string together
with the running index, use switch/case pattern matching,
and access the numeric .value of the unicode scalar:
func modulo(_ s: String) -> Int? {
let facteurs = [7, 3, 1]
var result = 0
for (idx, uc) in s.uppercased().unicodeScalars.enumerated() {
let value: UInt32
switch uc {
case "<":
value = 0
case "0"..."9":
value = uc.value - UnicodeScalar("0").value
case "A"..."Z":
value = uc.value - UnicodeScalar("A").value + 10
default:
return nil
}
result += Int(value) * facteurs[idx % facteurs.count]
}
return result % 10
}
This compiles with both Swift 3 and 4. Of course you could also
throw an error instead of returning nil for invalid input.
Note that "<", "0", "9" etc.
in the switch statement are inferred from the context as UnicodeScalar,
not as String or Character, therefore "0"..."9" (in this context)
is a ClosedRange<UnicodeScalar> and uc can be matched against
that range.
Something like this works for me:
"A".utf16.first! + 2 //comes out to 67
Careful with the forced unwrap "!"
If you need the scalars value you can do
"A".unicodeScalars.first!.value + 2
More reading can be done on this here in the SPL.
For the c Character type value you could do this:
String(c).unicodeScalars.first!.value + 2
Here is an attempt to mod the function:
func modulo(s: String) -> Int? {
var result = 0
var factors = [7,3,1]
for (i, c) in s.uppercased().characters.enumerated() {
let char = String(c)
var val: Int
if char == "<" {
val = 0
} else if "0123456789".contains(char) {
val = Int(char.unicodeScalars.first!.value - "0".unicodeScalars.first!.value)
} else if "ABCDEFGHIJKLMNOPQRSTUVWXYZ".contains(char) {
val = Int(char.unicodeScalars.first!.value - 55)
} else {
return nil
}
result += val * factors[(i) % 3]
}
return result % 10
}
This is in swift 3...in 4 I believe you can just iterate over the string without converting to Chars