how to use calculated values from past steps to determine next step - ios

I'm working on a Survey app based on ResearcKit, where I need to determine the next step based on the sum of the results of the last five steps. This is my very first time programming in swift, and it has been a while since I've done OOP, and I keep making rookie mistakes. Anyway:
In each those five steps, the user chooses one of three sentences that fits her situation the best. Each of these choices results in an answervalue (0, 1 or 2)
If the sum of the answervalues <= 5 the next step is a completionstep (no significant reason for worry), if the sum > 5 they will get a completionstep with tips on what steps to take for help.
A simplified piece of code (with only two steps to sum up) shows where my problem is - it seems like I'm doing something stupid there. Google helped me a lot with all the other predicates for my Survey, mostly with the NSCompundPredicate, but rewriting this to a situation where I could use that seems like the (very) long way around a seemingly simple problem.
Any help is greatly appreciated.
...
let choices = [
ORKTextChoice(text: “never”, value: "0" as NSCoding & NSCopying & NSObjectProtocol),
ORKTextChoice(text: “sometimes”, value: "1" as NSCoding & NSCopying & NSObjectProtocol),
ORKTextChoice(text: “all the time”, value: "2" as NSCoding & NSCopying & NSObjectProtocol)]
 
let question1AnswerFormat : ORKTextChoiceAnswerFormat = ORKAnswerFormat.choiceAnswerFormat(with: .singleChoice, textChoices: choices)
let question1 = ORKQuestionStep(identifier: "question1", title: “question1”, answer: question1AnswerFormat)
question1.isOptional = false
question1.text = “Do you ever visit StackOverflow?”
steps += [question1]
    
let question2AnswerFormat : ORKTextChoiceAnswerFormat = ORKAnswerFormat.choiceAnswerFormat(with: .singleChoice, textChoices: choices)
let question2 = ORKQuestionStep(identifier: "question2", title: “question2”, answer: question2AnswerFormat)
question2.isOptional = false
question2.text = “Do you ever post questions on StackOverflow?”
steps += [question2]
//This part is rubbish, it doesn’t even compile, but I hope this shows what I’m looking for
let sum :Int = Int(ORKResultSelector(resultIdentifier: question1.identifier) as! String)! + Int(ORKResultSelector(resultIdentifier: question2.identifier) as! String)!
// ===
let predicate = NSPredicate(value: (sum>2))
let rule = ORKPredicateStepNavigationRule(resultPredicatesAndDestinationStepIdentifiers: [(resultPredicate: predicate, destinationStepIdentifier: risk.identifier)], defaultStepIdentifierOrNil: norisk.identifier)
task.setNavigationRule(rule, forTriggerStepIdentifier: question2.identifier)
...

Related

How to format dates and times in the form of "2 hours before"?

I want to format dates and times in the form of "5 hours before", just like the Calendar app does. And I also want the fomatted string be localized for many languages.
There is a new API called RelativeDateTimeFormatter introduced in iOS 13. However, this API can only produce localized string like "2 hours ago" and "in 2 hours". This is not suitable for my purpose.
I wonder if there is an API that provides the functionality I'm looking for.
If you are interested how Apple does it, the localization is available in EventKitUI framework.
import EventKitUI
...
let bundle = EventKitUIBundle()!
// access the string that adds "before" information to an interval
let beforeKey = bundle.localizedString(forKey: "%# before", value: nil, table: nil)
// access the string that localizes some interval (days, in this case)
let intervalKey = bundle.localizedString(forKey: "interval_days_long", value: nil, table: nil)
// use the strings with a value
print(String(format: beforeKey, String(format: intervalKey, 1))) // 1 day before
print(String(format: beforeKey, String(format: intervalKey, 3))) // 3 days before
The localization dictionary contains proper pluralization for number values 1-9.
You can do exactly the same. I don't recommend using Apple's localization strings since these are not documented.
If it is possible for you to localized the before part, maybe consider using DateComponentsFormatter?
(Maybe this is more suitable as a comment, but seems to be too long)
For example:
let componentsList = [
DateComponents(minute:5),
DateComponents(minute:10),
DateComponents(minute:15),
DateComponents(minute:30),
DateComponents(hour:1),
DateComponents(hour:2),
DateComponents(day:1),
DateComponents(day:2),
DateComponents(weekOfMonth:1)
]
for components in componentsList {
if let text = DateComponentsFormatter.localizedString(from: components, unitsStyle: .full) {
print("\(text) before")
}
}

Up-to-date list of built-in Core Image filters?

Apple's Core Image Filter Reference, which describes all of the built-in CIFilters, is marked as "no longer being updated".
Looks like it was last updated in 2016. Since then, WWDC videos for 2017 and 2018 have announced additional filters (which, indeed, don't appear on this page).
Does anybody know of a more up-to-date list of built-in Core Image filters?
(Question has also been asked, but so far not answered, on the Apple Dev Forum.)
I've created a website called CIFilter.io which lists all the built-in CIFilters and a companion app which you can use to try the filters out if you like. This should have all the up to date CIFilter information - I've updated it for iOS 13 and intend to continue to keep it updated.
More info about the project is available in this blog post.
I created a small project to query an iOS device and (1) list out all available filters and (2) list everything about each input attributes. This project can be found here.
The relevant code:
var ciFilterList = CIFilter.filterNames(inCategories: nil)
This line creates a [String] of all available filters. If you only wish for all available filters of category "CICategoryBlur", replace the nil with it.
print("=======")
print("List of available filters")
print("-------")
for ciFilterName in ciFilterList {
print(ciFilterName)
}
print("-------")
print("Total: " + String(ciFilterList.count))
Pretty self-explanatory. When I ran this on an iPad mini running iOS 12.0.1, 207 filters were listed. NOTE: I have never tried this on macOS, but since it really doesn't use UIKit I believe it will work.
let filterName = "CIZoomBlur"
let filter = CIFilter(name: filterName)
print("=======")
print("Filter Name: " + filterName)
let inputKeys = filter?.inputKeys
if inputKeys?.count == 0 {
print("-------")
print("No input attributes.")
} else {
for inputKey in inputKeys! {
print("-------")
print("Input Key: " + inputKey)
if let attribute = filter?.attributes[inputKey] as? [String: AnyObject],
let attributeClass = attribute[kCIAttributeClass] as? String,
let attributeDisplayName = attribute["CIAttributeDisplayName"] as? String,
let attributeDescription = attribute[kCIAttributeDescription] as? String {
print("Display name: " + attributeDisplayName)
print("Description: " + attributeDescription)
print("Attrbute type: " + attributeClass)
switch attributeClass {
case "NSNumber":
let minimumValue = (attribute[kCIAttributeSliderMin] as! NSNumber).floatValue
let maximumValue = (attribute[kCIAttributeSliderMax] as! NSNumber).floatValue
let defaultValue = (attribute[kCIAttributeDefault] as! NSNumber).floatValue
print("Default value: " + String(defaultValue))
print("Minimum value: " + String(minimumValue))
print("Maximum value: " + String(maximumValue))
case "CIColor":
let defaultValue = attribute[kCIAttributeDefault] as! CIColor
print(defaultValue)
case "CIVector":
let defaultValue = attribute[kCIAttributeDefault] as! CIVector
print(defaultValue)
default:
// if you wish, just dump the variable attribute to look at everything!
print("No code to parse an attribute of type: " + attributeClass)
break
}
}
}
}
}
print("=======")
Again, fairly self-explanatory. The app I'm writing only works with filters using a single CIImage and with attributes restricted to NSNumber, CIColor, and CIVector, so things will fall to the default part of the switch statement. However, it should get you started! If you wish to see the "raw" version, jut look at the attribute variable.
Finally, I'd recommend something developed by Simon Gladman called Filterpedia. It's an iPad app (restricted to landscape) that allows you to experiment with pretty much all available filters along with all attributes with default/max/min values. Be aware of two things though. (1) It's written in Swift 2, but the is a Swift 4 fork here. (2) There are also numerous custom filters using custom CIKernels.

How to generate random number without repeating in Swift

I'm extremely new to Swift and programming in general so please be patient with me while I'm trying to get the hang of this.
I've been following a beginners course in Swift from Treehouse and managed to develop a simple app that generates random quotes. So far so good. Now before moving on to more advanced lessons I figured I'd try to update the existing app just to make sure I get some solid practice before moving on.
So here's my question: I've managed to generate a random number through the GameKit framework, but the problem is that sometimes quotes appear consecutively. How can I avoid this from happening?
Here's my code:
import GameKit
struct FactProvider {
let facts = [
"Ants stretch when they wake up in the morning.",
"Ostriches can run faster than horses.",
"Olympic gold medals are actually made mostly of silver.",
"You are born with 300 bones; by the time you are an adult you will have 206.",
"It takes about 8 minutes for light from the Sun to reach Earth.",
"Some bamboo plants can grow almost a meter in just one day.",
"The state of Florida is bigger than England.",
"Some penguins can leap 2-3 meters out of the water.",
"On average, it takes 66 days to form a new habit.",
"Mammoths still walked the Earth when the Great Pyramid was being built."
]
func randomFact() -> String {
let randomNumber = GKRandomSource.sharedRandom().nextInt(upperBound: facts.count)
return facts[randomNumber]
}
}
You can store last random number or last fact in a variable and check it in your randomFact function. Like this:
var lastRandomNumber = -1
func randomFact() -> String {
let randomNumber = GKRandomSource.sharedRandom().nextInt(upperBound: facts.count)
if randomNumber == lastRandomNumber {
return randomFact()
} else {
lastRandomNumber = randomNumber
return facts[randomNumber]
}
}

Problems with structuring and retrieving data from Firebase in Swift

I am designing a litte quiz app but I'm having trouble while retrieving the game data.
As you can see in the picture I have an JSON object that contains many single games. Each single game has a unique id. My first problem is that each of the games can be available in multiple languages. I know that I could download the hole snap and then looping throw each game but that would mean really long loading times while the app is growing.
In short form:
I need to retrieve the following data from the JSON above:
A random game wich is available in a specific language (need to have the key en for example)
All games that are available in "en" but not yet in "de"
If it is easier to restructure the data in the JSON, please tell me.
Thanks for helping me.
Answer to your first part :-
let enRef = FIRDatabase.database().reference().child("singleGames").child(singleGamesUID).child("en")
enRef.observeEventType(.Value, withBlock: {(snap) in
if let enQuizQuestion = snap.value as? [String:AnyObject]{
//Question exists : Retrieve Data
}else{
//Question in english doesn't exist
}
})
For your second part
Since you are trying to save iteration time might i suggest you also save your singleGames id in a separate languagesBased nodes, there is a command in firebase that allows you to search for some keyValues in your child node's , but even that i think would be executing a search algorithm which might be a little more time consuming :--
appServerName:{
singleGames :{
uid1:{......
......
...},
uid2:{......
......
...},
uid3:{......
......
...}
},
enQuestions:{
uid3 : true
}
deQuestions:{
uid1 : true,
uid2 : true
}
}
Now all you gotta do :-
let rootRef = FIRDatabase.database().reference().child("deQuestions").observeEventType(.Value, withBlock: {(qSnap) in
if let qDict = qSnap.value as? [String:AnyObject]{
for each in qDict as [String:AnyObject]{
let deUID = each.0
}
}else{
//No question in dutch language
}
})

Displaying strings in iOS randomly without repeating them

I'm making a quiz app. The app uses a .json file as the 'database' of questions and answers. This .json file looks as follows...
{
"id" : "1",
"question": "Earth is a:",
"answers": [
"Planet",
"Meteor",
"Star",
"Asteroid"
],
"difficulty": "1"
}
...and just keeps going for over 500 questions.
I display the questions randomly by using the following code:
let randomNumber = Int(arc4random_uniform(UInt32(allEntries.count)))
LoadQuestion(randomNumber)
This work fine, but because it selects the questions randomly I'm finding that questions are repeating too regularly (even though I have over 500 questions!).
I've researched this extensively and think perhaps I've read too much as I'm now quite confused. I've read about 'seeding', about saving an index of questions asked, and trying to use NSUserDefault.
In short, how can I modify my code to achieve one of the following outcomes instead:
To not repeat any questions at all, but to stop asking questions when all questions have been asked once;
Similar to 1 above, but have the number of questions being asked set by the user rather than asking all questions in the database;
To not repeat any questions at all until all questions have been asked first; or,
To not repeat any questions that have previously been answered correctly, but those that were answered incorrectly may be repeated.
Below is what I think are the relevant bits of code:
LoadAllQuestionsAndAnswers()
let randomNumber = Int(arc4random_uniform(UInt32(allEntries.count)))
LoadQuestion(randomNumber)
func LoadAllQuestionsAndAnswers()
{
let path = NSBundle.mainBundle().pathForResource("content", ofType: "json")
let jsonData : NSData = NSData(contentsOfFile: path!)!
allEntries = (try! NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions.MutableContainers)) as! NSArray
//println(allEntries)
}
func LoadQuestion(index : Int)
{
let entry : NSDictionary = allEntries.objectAtIndex(index) as! NSDictionary
let question : NSString = entry.objectForKey("question") as! NSString
let arr : NSMutableArray = entry.objectForKey("answers") as! NSMutableArray
//println(question)
//println(arr)
labelQuestion.text = question as String
let indices : [Int] = [0,1,2,3]
//let newSequence = shuffle(indices)
let newSequence = indices.shuffle()
var i : Int = 0
for(i = 0; i < newSequence.count; i++)
{
let index = newSequence[i]
if(index == 0)
{
// we need to store the correct answer index
currentCorrectAnswerIndex = i
}
let answer = arr.objectAtIndex(index) as! NSString
switch(i)
{
case 0:
buttonA.setTitle(answer as String, forState: UIControlState.Normal)
break;
case 1:
buttonB.setTitle(answer as String, forState: UIControlState.Normal)
break;
case 2:
buttonC.setTitle(answer as String, forState: UIControlState.Normal)
break;
case 3:
buttonD.setTitle(answer as String, forState: UIControlState.Normal)
break;
default:
break;
}
}
buttonNext.hidden = true
// we will need to reset the buttons to reenable them
ResetAnswerButtons()
}
#IBAction func PressedButtonNext(sender: UIButton) {
print("button Next pressed")
let randomNumber = Int(arc4random_uniform(UInt32(allEntries.count)))
LoadQuestion(randomNumber)
}
If I understand your requirements correctly:
You want to persist the randomly generated numbers between View Controller instances and application launches
you never want to repeat any question
A quick way to achieve this is to use NSUserDefaults to keep track of the order and value of (pseudo)random numbers from your PRNG. This way, you can instantiate the array of avaiable questions on instantaition by replaying previous dice rolls, removing those questions from the pool alongside in the right order.
This also lets you handle the PRNG as a black box, not caring about seeding and replay.
Also, make sure to remove any chosen questions from the current pool for any dice roll.
Side note: You are not following naming conventions - functions are to be starting lower case, for example.
For your own good and readability, please follow prevalent conventions, at least when sharing your code with others. Better yet, all the time.
Edit:
To be more verbose:
NSUserDefaults can save small amounts of data for you in an easy way. If you 'just want to remember an array of numbers', it's the go to place.
suppose your data is helt in an instance variable like so:
var questions : [Question] = // load the questions - carefully, see below.
you can easily remove the question your PSNG (pseudo-random number generator) selects like so:
let randomNumber = Int(arc4random_uniform(UInt32(questions.count)))
questions.removeAtIndex(randomNumber)
Thus the next dice roll (e.g. invocation of your PSNG) is guaranteed not to select the question already asked because it is no longer in the pool of avaiable questions it selects from.
This approach does need you to save all your random numbers in the right order to 'replay' what has happened before when you are instantiating a new instance (for example, because the app has been reopened). That's where NSUserDefaults' setObject:forKey: and objectForKey: come into play when loading the questions initially and 'remembering' every new dice roll.
I hope, this covers what you might didn't understand before.

Resources