Get Quote and its author using Swift - arrays

I am very new to Swift, and I am trying to make an app of quotes, but I want to display the Quote and its own author both in a separate UILabel. But I don't know how to do this, because I have a randomQuote function that returns a random index for my array but the array only displays the quote, I want to get a random quote and get the author of the quote.
Here is what I have done so far:
var quotes = ["'Stay Hungry, Stay Foolish.'", "'Life is what happens while you are busy making other plans.'", ""]
var authors = ["Steve Jobs", "John Lennon"]
func getRandomQuote() -> String{
//get random index
var arrayCount = UInt32(quotes.count)
var randomNumber = arc4random_uniform(arrayCount)
var finalNumber = Int(randomNumber)
return quotes[finalNumber]
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var randomImage: String = imageChange[randomNumber]
self.motivationText.text = getRandomQuote()
self.imageInspire.image = UIImage(named: randomImage)
}
I was thinking about using a dictionary but I think it won't work :/

If your authors and quotes are always in the same order you can use the same finalNumber for both the quotes & author array.
You can set both labels inside of getRandomQuote and remove the String return, then you can call it from viewDidLoad.
A dictionary would work, you can treat it's keys as an array for random reasons and then just use the key/value pair to populate your labels.
Example if quotes & authors are always in the same order:
func getRandomQuote() {
//get random index
var arrayCount = UInt32(quotes.count)
var randomNumber = arc4random_uniform(arrayCount)
var finalNumber = Int(randomNumber)
self.motivationText.text = quotes[finalNumber]
self.authorName.text = authors[finalNumber] // Just an assumption on label name
}
override func viewDidLoad() {
super.viewDidLoad()
getRandomQuote()
}
Dictionary example:
let quotesWithAuthors = ["Steve Jobs":"Stay Hungry, Stay Foolish","Alan Kay":"Simple things should be simple, complex things should be possible","Bill Gates":"I'm not fake Steve Jobs"]
let authorsArray = quotesWithAuthors.keys.array
let randomQuoteKeyIndex = Int(arc4random_uniform(UInt32(authorsArray.count - 1)))
self.authorName.text = authorsArray[randomQuoteKeyIndex]
self.otivationText.text = quotesWithAuthors[authorsArray[randomQuoteKeyIndex]]
In essence they are very similar from an execution point of view but may be easier to store this way. Though you may run into problems if you have multiple quotes from the same author using an author name as a dictionary - in that case you may want to flip the author & quote.

Related

How do I calculate the sum of the respective variables of a custom type array?

I am new to coding in Swift. My goal is to build a variable containing the sum of a number of custom class variables.
To speak in clear terms:
I have a custom class called "Entry" which has the Double variable "sum", e.g. 50.00.
I am using a ViewController to let the user create a new entry with text field inputs, inter alia a sum of the new entry. This new entry element is then appended to an array of type Entry when the relevant button is pressed.
#IBAction func addEntry(_ sender: UIButton){
// take user input
let name = nameTextField.text ?? ""
let sum = Double(sumTextField.text!) ?? 0.0
let category = selectedCategory
// save user input in new entry
let newEntry = Entry(name: name, sum: sum, category: category)
entries.append(newEntry)
saveEntries()
os_log("Saved new entry successfully.", log: OSLog.default, type: .debug)
In another ViewController, I want to access the array "entries" and build the sum of all sum variables of all Entry elements, e.g. sum of Entry 1 + sum of Entry 2 + sum of Entry 3
My current attempt in coding is as follows:
var entriesDatabase = [Entry]()
//extract sum of entries and build sumOfEntries
var sumArray = [Double]()
for entry in entriesDatabase {
sumArray.append(entry.sum)
}
sumOfEntries = sumArray.reduce(0, +)
The entries array from the first View Controller is saved by using the NSKeyedArchiver and loaded by NSKeyedUnarchiver.unarchiveObject(withFile:) in the second View Controller before calling the function above (which, I am aware, has been deprecated, but it works for my current purposes).
I used the print function to isolate the issue and as far as I can see, the sumOfEntries always remains 0.0, no matter how many Entry elements I create (which itself seems to work, though).
Has anyone a clue what I am doing wrong?
EDIT: The issue to me really seems to be that the calculation does not work rather than the passing of the data from one view to another. Somehow, the arrays always remain empty. The passage of the data works via saving it persistently on the drive and then loading it with the NSKeyedArchiver functions. For clarity see the following code:
/MARK: calculate and display balance
func calculate(){
//load data from user defaults
recurringCalculationValue = UserDefaults.standard.value(forKey: "recurringExpenses") ?? 0.0
monthlyIncomeCalculationValue = UserDefaults.standard.value(forKey: "monthlyIncome") ?? 0.0
//extract sum of entries and build sumOfEntries
var sumArray = [Double]()
for entry in entriesDatabase {
sumArray.append(entry.sum)
}
sumOfEntries = sumArray.reduce(0, +)
//cast the user defaults into Double
let mICV = monthlyIncomeCalculationValue as! Double
let rCV = recurringCalculationValue as! Double
//convert the Strings to Double! and calculate balance
balance = Double(mICV) - Double(rCV) - sumOfEntries
//display balance in sumLabel
sumLabel.text = "\(balance)"
//debugging
print("balance = \(balance)")
print("sumOfEntries = \(sumOfEntries)")
print("monthlyIncomeCalculationValue = \(monthlyIncomeCalculationValue)")
print("recurringCalculationValue = \(recurringCalculationValue)")
print("UserDefault monthlyIncome = \(String(describing: UserDefaults.standard.value(forKey: "monthlyIncome")))")
print("UserDefault recurringExpenses = \(String(describing: UserDefaults.standard.value(forKey: "recurringExpenses")))")
}
//this function is called when the ViewController is opened
#IBAction func unwindToThisViewController(segue: UIStoryboardSegue) {
//Load saved entries into entries array if entries were saved
let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] as String
let url = NSURL(fileURLWithPath: path)
if let pathComponent = url.appendingPathComponent("entries") {
let filePath = pathComponent.path
let fileManager = FileManager.default
if fileManager.fileExists(atPath: filePath) {
print("File available.")
entriesDatabase = loadEntries()!
print("Database loaded.")
} else {
print("File not available.")
}
} else {
print("File path not available.")
}
calculate()
}
private func loadEntries() -> [Entry]? {
return NSKeyedUnarchiver.unarchiveObject(withFile: Entry.ArchiveURL.path) as? [Entry]
}
I hope, that makes my problem clearer and thanks again!
The way I see it, in var entriesDatabase = [Entry]() you create a new array for objects of type Entry, which is (of course) initially empty. Therefore, the sum of the values will be 0.
What you want is to cache the values, e.g. in your saveEntries()-Function. You may want to take a look at UserDefaults which stores information in a map-like matter.

Generate random Strings without a shuffle and repetition of previous ones?

My code already generates a random String of an array when I press a button, but sometimes a String gets repeated. What do I have to do so that the String "Mango" only gets called again when all the other Strings where already called without using a shuffle, I want to call one String at a time?
Example: "Mango", "Kiwi", "Banana", "Pineapple", "Melon", "Mango", "Kiwi",.....
Here is my code:
var array = ["Mango", "Banana", "Apple","Kiwi", "Melon", "Pineapple"]
let fruits = Int(arc4random_uniform(UInt32(array.count)))
print(array[fruits])
In order to avoid repetitions you need to keep track of which fruits have previously been seen. There are several ways to do that an all of the proposed solutions do it in one way or another.
For your specific use case, you will need this tracking to be retained outside of the code executed by the button (in your view controller for example).
Here is a generalized structure that could help with this:
(you can define it inside the view controller if this is a one-time thing or outside of it if you intend to reuse the mechanism elsewhere)
struct RandomItems
{
var items : [String]
var seen = 0
init(_ items:[String])
{ self.items = items }
mutating func next() -> String
{
let index = Int(arc4random_uniform(UInt32(items.count - seen)))
let item = items.remove(at:index)
items.append(item)
seen = (seen + 1) % items.count
return item
}
}
To use it, you would declare a variable (in your VC) that will keep track of your fruits:
var fruits = RandomItems(["Mango", "Banana", "Apple","Kiwi", "Melon", "Pineapple"])
And in the button's code use that variable (fruits) to print a non-repeating fruit name at each execution
func buttonPressed() // <- use your function here
{
print( fruits.next() )
}
You need to implement some logic. It's quite easy if you think harder. Run this in your Playground, or if you fully understand this block of code, you can do this in your project already.
var array = ["Mango", "Banana", "Apple","Kiwi", "Melon", "Pineapple"]
var selectedIndices = [Int]()
for _ in 1...20 {
let randomFruitIndex = Int(arc4random_uniform(UInt32(array.count)))
// Print only if not yet printed once
if !selectedIndices.contains(randomFruitIndex) {
print(array[randomFruitIndex])
selectedIndices.append(randomFruitIndex)
}
// Reset
if selectedIndices.count == array.count {
print("----- CLEARING SELECTED INDICES----")
selectedIndices.removeAll()
}
}
So as you can see, we are adding each generated random number (in your case, it's the fruits variable.) into an array of Int. Then if the number of selectedIndices is equal to the count of the array of fruits, clear all the stored selectedIndices.
OUTPUT:
Pineapple
Melon
Mango
Kiwi
Banana
Apple
----- CLEARING SELECTED INDICES----
Mango
Melon
This is an adaption from the accepted answer of the linked topic in my comment:
var source = ["Mango", "Banana", "Apple","Kiwi", "Melon", "Pineapple"]
var usedElements = [String]()
func choosePseudoRandomElement() -> String {
if source.count == 0 {
source = usedElements
usedElements = []
}
let randomIndex = Int(arc4random_uniform(UInt32(source.count)))
let randomItem = source[randomIndex]
usedElements.append(randomItem)
source.remove(at: randomIndex)
return randomItem
}
for _ in 1...18 {
print("Item: \(choosePseudoRandomElement())")
}
One potential issue with this solution is that it may happen, that the last element of one complete iteration also occurs as the first element of the second iteration. You can handle that case by comparing the randomly chosen item with the item which was chosen before (use a while loop until the items doesn't match anymore).
Also, this does remove elements from the source array. If you do not want that, create a copy of the source array.

How to order PFObjects based on its creation date?

I have some user comments stored in a database (parse-server) that I would like to would like to display on my viewController's viewDidLoad(). I can easily pull the comment objects as follows:
super.viewDidLoad()
func query(){
let commentsQuery = PFQuery(className: "Comments")
commentsQuery.whereKey("objectId", equalTo: detailDisclosureKey)
commentsQuery.findObjectsInBackground { (objectss, error) in
if let objects = objectss{
if objects.count == 1{
for object in objects{
self.unOrderedComments.append(object)
}
}
}
}
}
This query dumps all of the of the comments in the unOrederedComments array. Each comment is added to the database with a createdAt property automatically being added relating the exact time of its creation. This property is a string with (as an example) the form: "2017-08-13T19:31:47.776Z" (the Z at the end is at the end of every string... not exactly sure why its there but its constant). Now, each new comment is added in order to the top of database and thus any queried result should be in order regardless. However, I would like to make sure of this by reordering it if necessary. My general thought process is to use .sorted, but I cannot figure out how to apply this to my situation
func orderComments(unOrderComments: [PFObject]) -> [PFObject]{
let orderedEventComments = unOrderedEventComments.sorted(by: { (<#PFObject#>, <#PFObject#>) -> Bool in
//code
})
}
This is the generic set up but I cannot, despite looking up several examples online figure out what to put in the <#PFObject#>'s and in the //code. I want to order them based on the "createdAt" property but this is not achieved via dot notation and instead requires PFObject["createdAt"] and using this notation keeps leading to error. I feel as so though I may need to set up a custom predicate but I do not know how to do this.
I was in the same situation, what I did was to first create an array of structs with the data I downloaded where I turned the string createdAt into a Date, then used this function:
dataArrayOrdered = unOrderedArray.sorted(by: { $0.date.compare($1.date) == .orderedAscending})
(.date being the stored Date inside my array of strcuts)
Try this code, notice that I assumed you have a variable name called ["Comments"] inside your Parse database, so replace if necessary. Also, I realised that createdAt it's in Date format, so there was no need to change it from String to Date, chek if it works the same for you, if it doesn't refer to this: Swift convert string to date.
struct Comment {
var date = Date()
var comment = String()
}
var unOrderedComments: [Comment] = []
var orderedComments = [Comment]()
override func viewDidLoad() {
super.viewDidLoad()
query()
}
func query(){
let commentsQuery = PFQuery(className: "Comments")
commentsQuery.findObjectsInBackground { (objectss, error) in
if let objects = objectss{
if objects.count >= 1{
for object in objects{
let newElement = Comment(date: object.createdAt!, comment: object["Comments"] as! String)
self.unOrderedComments.append(newElement)
print(self.unOrderedComments)
}
}
self.orderedComments = self.unOrderedComments.sorted(by: { $0.date.compare($1.date) == .orderedAscending})
print(self.orderedComments)
}
}
}

"Extra argument in call" error when using zip() to iterate over multiple arrays

I'm trying to iterate over multiple arrays then place everything into one other array.
This is my class that I'm going to loop into:
class Place {
var names = [String]()
var messages = [String]()
var latidudes = [Double]()
var longitudes = [Double]()
var locations = [CLLocationCoordinate2D]()
}
And this is my function:
private func placesArrayLoop() {
let CoffeeShopNames = ["aCoffee","bCoffee", "cCoffee"]
let messages = ["message0", "message1", "message2"]
let latitudes = [40.232320, 40.232321, 40.232322]
let longitudes = [-95.388069, -95.388068, 95.388067]
for (name, message, latitude, longitude) in zip(CoffeeShopNames, messages, latitudes, longitudes) {
let place = Place()
place.names.append(name)
place.messages.append(message)
place.latitudes.append(latitude)
place.longitudes.append(longitude)
}
}
It is giving me the error, "extra argument in call pointing to latitudes in the zip array line. I'm assuming it's a syntax error new to swift 3, but I've looked around and cant find how to fix it. But the code bellow works...
let strArr1 = ["Some1", "Some2", "Some3"]
let strArr2 = ["Somethingelse1", "Somethingelse2", "Somethingelse3"]
for (e1, e2) in zip(strArr1, strArr2) {
print("\(e1) - \(e2)")
}
So now I'm really confused.
The zip() function only takes two arguments (hence why your second example works).
http://swiftdoc.org/v3.0/func/zip/
You are trying to pass it four arguments.

Swift addObject

So, I'm learning how to get data from DB with JSON and then put the data on some array. Problem accours on last line, citiesArray.addObject(City()), when I need to put all data from object city (id, name, state,...) into array.
I was looking line by line with compiler, and basically everything is fine except that at the end, my array is still empty (its value is nil)?
for (var i=0;i<jsonArray.count;i++){
//Create city objec
var cID: AnyObject? = jsonArray.objectAtIndex(i).objectForKey("id") as NSString
var cName: AnyObject? = jsonArray.objectAtIndex(i).objectForKey("cityName") as NSString
var cState: AnyObject? = jsonArray.objectAtIndex(i).objectForKey("cityState") as NSString
var cPopulation: AnyObject? = jsonArray.objectAtIndex(i).objectForKey("cityPopulation") as NSString
var cCountry: AnyObject? = jsonArray.objectAtIndex(i).objectForKey("country") as NSString
//add city obj (i have City class) to city array
var city = City()
city.cityID = cID as NSString
city.cityName = cName as NSString
city.cityState = cState as NSString
city.cityPopulation = cPopulation as NSString
city.cityCountry = cCountry as NSString
citiesArray.addObject(City())
}
A couple of issues:
You suggested that you were trying to add the city with the following line of code:
citiesArray.addObject(City())
The City() construct will instantiate a new, blank City object. So that line of code would, best case scenario, add a blank City object to your array, which is not what you intended.
When you add the city to your citiesArray, you should simply:
citiesArray.addObject(city)
You say you've defined your citiesArray like so:
var citiesArray: NSMutableArray!
You also need to instantiate an object for this variable (i.e. create an object to which this variable will now point), e.g.:
citiesArray = NSMutableArray()
You are reporting, though, that at the end of this loop, that citiesArray is nil. Really?!? But if you tried to call the addObject method and citiesArray was nil, you could have received a fatal error: "unexpectedly found nil while unwrapping an Optional value".
So, if citiesArray was nil, then jsonArray must have been empty, too. Or for some reason you didn't even get to this loop. I would suggest (a) logging jsonArray; and (b) log or put breakpoint inside this loop and confirm you're even getting in here like you think you are.
Also, check the timing of this (i.e. make sure your statement logging citiesArray is actually taking place after this routine that populates it). I know that sounds crazy, but if you are retrieving the data from some network resource asynchronously, you could have some timing related issues.
Since you're writing Swift code, you might consider using Swift arrays. For example, define your array variable as
var citiesArray: [City]!
And instantiate it with:
citiesArray = [City]()
And add objects to it with:
citiesArray.append(city)
I am pretty sure you need to use the append function:
citiesArray.append(city)
or
if you want to append at the start of the array
citiesArray.insert(city, atIndex: 0)
instead of
citiesArray.addObject(City())
here is a little example: Syntax might not be 100% not on comp with xcode right now.
var strA:String = "apple"
var strB:String = "pineapple"
var strArr = ["kiwi", "mango", "lime"]
strArr.append(strA)
println(strArr.count) //4 ["kiwi", "mango", "lime", "apple"]
citiesArray.insert(strB, atIndex: 0)
println(strArr.count) //5 ["pineapple", "kiwi", "mango", "lime", "apple"]

Resources