PHP programer here struggling with Swift.
How do I create a Struct (Mutidimentional array in PHP) and walk through the elements to check a value?
Here is the code I am trying but it fails:
struct Alert: Codable {
let begin: Double
let end: Double
let color: String
let message: String
}
var alertStack = [ Int: Alert ]()
alertStack[60] = Alert(begin: 60.0,
end: 55.0,
color: "green",
message: "60 Seconds" )
alertStack[30] = Alert(begin: 30.0,
end: 25.0,
color: "yellow",
message: "30 Seconds!")
var alrtColor = "default" // Set default
var alrtText = "" // Set default
for alrt in alertStack {
if alrt.begin <= secondsLeft {
alrtColor = alrt.color // <-- Error
alrtText = alrt.message
}
}
Error is "Value of tuple type 'Dictionary<Int, Alert>.Element' (aka '(key: Int, value: Alert)') has no member 'begin'"
For a PHP guy this error message is confusing. I tried a few other things but can't seem to get the result I am looking for. I am hoping there is a simple fix or example that would work.
You can enumerate each key value pair in a dictionary like so:
for (i, alrt) in alertStack
Where "i" would be your int value
But: it may be better to try to find a more Swifty way of expressing your problem (what are you trying to do?) rather than trying to translate from PHP. For example, perhaps like:
let alerts: [Alert] = [
(60, 55, "green", "60 Seconds"),
(30, 25, "yellow", "30 Seconds!")
]
.map(Alert.init(begin:end:color:message:))
let color: String
let alertText: String
if let foundAlert = alerts.last(where: { $0.begin < secondsLeft }) {
color = foundAlert.color
alertText = foundAlert.message
}
else {
color = "default"
alertText = ""
}
(Maybe there is a reason but I don't know why you would want to have them in a dictionary keyed by their begin numbers)
If it helps I would imagine your problem may be expressed something like this:
struct Alert: Codable {
let color: String
let message: String
static let defaultAlert = Alert(color: "default", message: "")
static let thresholds: [ClosedRange<Double>: Alert] = [
55...60: Alert(color: "green", message: "60 Seconds"),
25...30: Alert(color: "yellow", message: "30 Seconds!")
]
}
func currentAlert(value: Double) -> Alert {
// Return the alert where the range of seconds contains
// the provided remaining seconds value
guard let found = Alert.thresholds.first(where: {
$0.key.contains(value)
}) else {
return .defaultAlert
}
return found.value
}
print(currentAlert(value: 57)) // 60 Seconds
print(currentAlert(value: 42)) // default
print(currentAlert(value: 26)) // 30 Seconds!
You're right, that's not a big deal. Everything you need to do is write alrt.value.begin, alrt.value.color and alrt.value.message. That's because of alertStack is Dictionary<Int, Alert> (or [Int: Alert], it's the same) type. And alrt is element of Dictionary that always has key and value properties. In your case key is Int, value is Alert
for alrt in alertStack {
if alrt.value.begin <= secondsLeft {
alrtColor = alrt.value.color
alrtText = alrt.value.message
}
}
Related
Good morning everyone - hope everyone is doing well!
I am learning to use ResearchKit and CareKit from the WWDC code-along. The pre-completed code-along file works great. The way they have it set up is there is a survey with a couple of sliding scale questions that when completed are stored in the CareKit Store and are extracted to complete the rings at the top of the app to show the person has completed the surveys for the day.
Now, I am trying to add a ORKTextChoice survey (multiple choice) to the code-along scale questions. So far I have been able to add in the ORKTextChoice survey to the existing scale questions. I can open the survey and select the answer choice; however when I click Done, I think the app tries to extract the answers from the ORKTextChoice to fill the ring I am getting an error.
else {
assertionFailure("Failed to extract answers from check in survey!")
return nil
}
> Thread 1: Fatal error: Failed to extract answers from check in survey!
Additionally, what I am trying to do here is to have multiple choice questions that store into CareKit as a number (where it says (in bold): ORKTextChoice(text: "Home oxygen use", value: 0 as NSNumber)) value so that later I can create a chart of the numbers based on the answer responses.
This is where I am getting the error (towards the very bottom of the full code below):
static let checkInIdentifier = "checkin"
static let checkInFormIdentifier = "checkin.form"
static let checkInPainItemIdentifier = "checkin.form.pain"
static let checkInSleepItemIdentifier = "checkin.form.sleep"
static let checkInSleepItemIdentifier2 = "checkin.form.sleep2"
static let TextChoiceQuestionStep = "checkin.form.sleep3"
static func checkInSurvey() -> ORKTask {
let painAnswerFormat = ORKAnswerFormat.scale(
withMaximumValue: 10,
minimumValue: 1,
defaultValue: 0,
step: 1,
vertical: false,
maximumValueDescription: "Very painful",
minimumValueDescription: "No pain"
)
let sleepAnswerFormat = ORKAnswerFormat.scale(
withMaximumValue: 12,
minimumValue: 0,
defaultValue: 0,
step: 1,
vertical: false,
maximumValueDescription: nil,
minimumValueDescription: nil
)
let sleepAnswerFormat2 = ORKAnswerFormat.scale(
withMaximumValue: 12,
minimumValue: 0,
defaultValue: 0,
step: 1,
vertical: false,
maximumValueDescription: nil,
minimumValueDescription: nil
)
let breathingChoices = [
ORKTextChoice(text: "Home oxygen use", value: 0 as NSNumber),
ORKTextChoice(text: "Cystic fibrosis", value: 1 as NSNumber),
ORKTextChoice(text: "Chronic lung disease", value: 2 as NSNumber),
]
let breathingAnswerFormat = ORKAnswerFormat.choiceAnswerFormat(
with: .singleChoice, //<-- .multipleChoice can allow for multiple options selected
textChoices: breathingChoices
)
let painItem = ORKFormItem(
identifier: checkInPainItemIdentifier,
text: "How would you rate your pain?",
answerFormat: painAnswerFormat
)
painItem.isOptional = false
let sleepItem = ORKFormItem(
identifier: checkInSleepItemIdentifier,
text: "How many hours of sleep did you get last night?",
answerFormat: sleepAnswerFormat
)
sleepItem.isOptional = false
let sleepItem2 = ORKFormItem(
identifier: checkInSleepItemIdentifier2,
text: "How many hours of sleep did you get last night2?",
answerFormat: sleepAnswerFormat2
)
sleepItem2.isOptional = false
let breathingStep = ORKFormItem(
identifier: TextChoiceQuestionStep,
text: "What of the following apply to your child's current breathing condition? (check all that apply)",
answerFormat: breathingAnswerFormat
)
breathingStep.isOptional = false
let formStep = ORKFormStep(
identifier: checkInFormIdentifier,
title: "Check In",
text: "Please answer the following questions."
)
formStep.formItems = [painItem, sleepItem, sleepItem2, breathingStep]
formStep.isOptional = false
let surveyTask = ORKOrderedTask(
identifier: checkInIdentifier,
steps: [formStep]
)
return surveyTask
}
static func extractAnswersFromCheckInSurvey(
_ result: ORKTaskResult) -> [OCKOutcomeValue]? {
guard
let response = result.results?
.compactMap({ $0 as? ORKStepResult })
.first(where: { $0.identifier == checkInFormIdentifier }),
let scaleResults = response
.results?.compactMap({ $0 as? ORKScaleQuestionResult }),
let painAnswer = scaleResults
.first(where: { $0.identifier == checkInPainItemIdentifier })?
.scaleAnswer,
let sleepAnswer = scaleResults
.first(where: { $0.identifier == checkInSleepItemIdentifier })?
.scaleAnswer,
let breathAnswer = scaleResults
.first(where: { $0.identifier == TextChoiceQuestionStep })?
.scaleAnswer
else {
assertionFailure("Failed to extract answers from check in survey!")
return nil
}
var painValue = OCKOutcomeValue(Double(truncating: painAnswer))
painValue.kind = checkInPainItemIdentifier
var sleepValue = OCKOutcomeValue(Double(truncating: sleepAnswer))
sleepValue.kind = checkInSleepItemIdentifier
var breathValue = OCKOutcomeValue(Double(truncating: breathAnswer))
sleepValue.kind = checkInSleepItemIdentifier
return [painValue, sleepValue, breathValue]
}
For reference, here is the code-along:
https://developer.apple.com/videos/play/wwdc2021/10068/
Here is the Git with the code-along code:
https://github.com/carekit-apple/WWDC21-RecoverApp
The ORKTextChoice survey questions (ie. Home oxygen use, etc.) came from here:
https://github.com/ResearchKit/ResearchKit/issues/919
I appreciate any help and guidance any of you can provide - Thank you!
Working on a bill splitting app and I am trying to create a dict with the amount everyone has to pay, e.g.:
{
"Person 1": 20,
"Person 2": 50,
"Person 3": 30
}
For now I am going for an even split by running this:
var dataDict: [String: Int] {
let numberOfPeople = 5
let billTotal = 100
let costPerPerson = billTotal / numberOfPeople
var dict: [String: Int] = [:]
ForEach(1 ..< numberOfPeople+1) { number in
dict["Person \(number)"] = costPerPerson
}
return dict
}
However I am getting the following error on the ForEach:
Type '()' cannot conform to 'View'; only struct/enum/class types can conform to protocols
What exactly am I doing wrong and how could I populate the dict with the for loop?
ForEach is not a for-loop in Swift. It's a View out of SwiftUI. The syntax you mean here is:
for number in 1...numberOfPeople {
dict["Person \(number)"] = costPerPerson
}
The following works in Playground:
func stringToInt(numberStr: String!) -> Int {
print(numberStr)
return Int(numberStr)!
}
let strNum1: String?
strNum1 = "1"
let result = stringToInt(numberStr: strNum1)
It returns 1 as expected.
In Xcode, a similar approach fails:
func stringToInt(numberStr: String!) -> Int {
print("\(numberStr!)")
let str = "\(numberStr!)"
print(Int(str))
return Int(str)!
}
The first print produces: Optional(1)
The second print produces: nil
The return statement fails because it is attempting to create an Int from a nil.
It must be something simple but I haven't been able to determine why it's not working. This is in Swift 3 and Xcode 8 BTW.
#Hamish:
In Xcode, I have a string with a numeric value. This:
print("number: (selectedAlertNumber) - unit: (selectedAlertUnit)")
...produces this:
number: Optional(1) - unit: Day
Then, I'm checking to see if either selectedAlertNumber of selecterAlertUnit != "-"
if selectedAlertNumber != "-" && selectedAlertUnit != "-" {
// set alert text
var unitStr = selectedAlertUnit
let alertNumber = stringToInt(numberStr: selectedAlertNumber)
if alertNumber > 1 {
unitStr.append("s")
}
let alertText = "...\(selectedAlertNumber) \(unitStr) before event."
alertTimeCell.setAlertText(alertText: alertText)
// set alert date/time
}
The let alertNumber = stringToInt... line is how I'm calling the function. I could just attempt the conversion there but I wanted to isolate the problem by wrapping the conversion in it's own function.
Using string interpolation to convert values to a String is usually not advised since the output may differ depending on optional status of the value. For example, consider these two functions:
func stringToInt(numberStr: String!) -> Int
{
print("\(numberStr!)")
let str = "\(numberStr!)"
return Int(str)!
}
func otherStringToInt(numberStr: String!) -> Int
{
print(numberStr)
let str = "\(numberStr)"
return Int(str)!
}
The only difference between these two is the ! in the second function when using string interpolation to get a String type value from numberStr. To be more specific, at the same line in function 1 compared to function 2, the string values are very different depending on whether or not the interpolated value is optional:
let str1: String = "1"
let str2: String! = "1"
let str3: String? = "1"
let otherStr1 = "\(str1)" // value: "1"
let otherStr2 = "\(str2)" // value: "Optional(1)"
let otherStr3 = "\(str2!)" // value: "1"
let otherStr4 = "\(str3)" // value: "Optional(1)"
let otherStr5 = "\(str3!)" // value: "1"
Passing otherStr2 or otherStr4 into the Int initializer will produce nil, since the string "Optional(1)" is not convertible to Int. Additionally, this will cause an error during the force unwrap. Instead of using string interpolation in your function, it would be better to just use the value directly since it's already a String.
func stringToInt(numberStr: String!) -> Int
{
return Int(numberStr)!
}
Let me know if this makes sense.
Also, my own personal feedback: watch out force unwrapping so frequently. In many cases, you're running the risk of getting an error while unwrapping a nil optional.
I'm new to Swift. Could someone please tell me what "??" stands for and what is its function in the context of
let snackName = favoriteSnacks[person] ?? "Candy bar"
I have included the complete code below:
struct Item{
var price: Int
var count: Int
}
class VendingMachine {
var inventory = [
"Candy bar": Item(price: 12, count: 7),
"Chips": Item(price: 10, count: 4),
"Pretzels": Item(price: 7, count: 11)
]
var coinsDeposited = 0
func dispenseSnack(snack: String){
print("dispensing \(snack)")
}
func vend(itemNamed name: String) throws {
guard var item = inventory[name] else {
throw VendingMachineError.InvalidSelection
}
guard item.count > 0 else {
throw VendingMachineError.OutOfStock
}
guard item.price <= coinsDeposited else {
throw VendingMachineError.InsufficientFunds(coinsNeeded: item.price - coinsDeposited)
}
coinsDeposited -= item.price
--item.count
inventory[name] = item
dispenseSnack(name)
}
}
let favoriteSnacks = [
"Alice": "Chips",
"Bob": "Licorice",
"Eve": "Pretzels",
]
func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {
let snackName = favoriteSnacks[person] ?? "Candy bar"
try vendingMachine.vend(itemNamed: snackName)
}
Its used to checking nil, if your value will be nil, you will assign default value.
let favoriteSnacks = [
"Alice": "Chips",
"Bob": "Licorice",
"Eve": "Pretzels",
]
from your code,
suppose person = "Eve"
let snackName = favoriteSnacks["Eve"] ?? "Candy bar"
it will first try to find value from dictionary, i.e. favoriteSnacks["Eve"] will give you value "Pretzels".
so it will assign value Pretzels to snackName variable.
suppose person = "Allen"
let snackName = favoriteSnacks["Allen"] ?? "Candy bar"
it will first try to find value from dictionary, i.e. favoriteSnacks["Allen"] will give you value nil.
In this case it will assign defalut value "Candy bar" to your variable snackName.
Let me please assume here based on its meaning in other languages:
let snackName = favoriteSnacks[person] ?? "Candy bar"
?? checks if the value of favoriteSnacks[person] is null. If it is NOT then the value of favoriteSnacks[person] will be assigned to snackName. If the value of favoriteSnacks[person] is null then "Candy bar" will be assigned to snackName.
It is used for having a default value in case favoriteSnacks[person] comes null.
Its the Nil Coalescing Operator basically giving a default for when the value does not exist.
see also Providing a default value for an Optional in Swift?
Ex. if you get only 5 keys and values in dictionary, but some time getting nil key or value, then error will be come for nil value.
So, here solution for that,
var strName = jsonObjectName ?? "Unknown"
var intAge = jsonObjectAge ?? 25
it means, if jsonObjectName is nil then automatically "Unknown" will be set to strName.
Note: default value provided by "?? value"
Simply explained,
let value = x ?? y
Is similar to writing,
let value = x != nil ? x! : y
It checks if x contains nil, if it doesn't then it force unwraps it, otherwise returns y in the above case.
I've write a simple code:
extension String {
func trailingSpaces (width: Int) -> String {
var s = "\(self)"
for i in count(s)..<width {
s = s + " "
}
return s
}
func leadingSpaces (width: Int) -> String {
var s = "\(self)"
for i in count(s)..<width {
s = " " + s
}
return s
}
}
class ViewController: UIViewController {
var users = ["Marco", "Gianni", "Antonio", "Giulio", "Franco"]
var ages = [29, 45, 17, 33, 37]
override func viewDidLoad() {
super.viewDidLoad()
var merged = [String: Int] ()
var totalAge = 0.0
for var i = 0; i < ages.count; i++ {
merged[users[i]] = ages[i]
}
for user in sorted(merged.keys) {
let age = merged[user]
totalAge += Double(age!)
let paddedUser = user.trailingSpaces(10)
let paddedAge = "\(age)".leadingSpaces(3)
println("\(paddedUser) \(age!)")
}
println("\n\(merged.count) users")
println("average age: \(totalAge / Double(merged.count))")
}
}
but I can't make it work the leadingSpaces function and I can't understand the reason, it's quite identical to the other extension func that works.
It give the error
fatal error: Can't form Range with end < start
on runtime
in case you run into this kind of problem, always do a println() of the variable you are using
println("\(age)") right before let paddedAge = "\(age!)".leadingSpaces(3)
reveals the problem
age is an optional, meaning that you are trying to do the padding on a String which has this value "Optional(17)"
Thus, your count(s) is higher than 3, and you have an invalid range
Your variable age is not an Int - it's an optional - Int?. You know this already as you are unwrapping it in the lines totalAge += Double(age!) and println("\(paddedUser) \(age!)") - but you are not unwrapping it in the failing line let paddedAge = "\(age)".leadingSpaces(3). The string being passed to leadingSpaces is not "17", it's "Optional(17)", which is why your padding function is failing, as the length is greater than the requested width.
Having said that, as the commentator #milo256 points out, Swift can only iterate upwards, and so unless you put a check on width >= .count in your padding functions they will crash at some point.