I have a Computer Science ISP to create a trivia game. I am trying to set a title of a button from an item in a Multi-Dimensional array. The array consists of 22 questions, type String arrays with 6 items in the array.
Looking like this :
let Q1:[String] = ["3","Who invented the telephone?","Rogers","Virgin Mobile","Graham Bell","Nikola Tesla","0"]
I have a shuffled Array that includes all 22 questions(22 Q1-Q22's) and I want a button's title to be the second item in the first array in the shuffledArray. But it gives me this error:
Cannot convert value of type 'Any' to expected argument type 'String?'
Based on the compile error you are getting, I would assume that if you tried to press option and click on shuffledArray, probably, it would be of type [[Any]] (depends on how you are declaring it). If you know that the element should be a String, you could cast it as follows:
if let qLabelText = shuffledArray[0][1] as? String {
qlabel.text = qLabelText
}
But
I would note that:
if you are pretty sure that shuffledArray should be an array of strings array, then you should -somehow- declare it [[String]] instead, thus there is no need to cast the element as string.
However, for such a case, creating your own custom Model for such a case (template) might be a better choice of doing it as a 2D array.
For instance:
struct QuestionModel {
var number: String
var content: String
var answer1: String
var answer2: String
var answer3: String
var answer4: String
var correctAnswer: String
}
Definitely, the structure of declaring the model is based on your requirement and may not be necessary to clone it as is.
You could declare your question as:
let question1 = QuestionModel(number: "3", content: "Who invented the telephone?", answer1: "Rogers", answer2: "Virgin Mobile", answer3: "Graham Bell", answer4: "Nikola Tesla", correctAnswer: "0")
Thus shuffledArray would be an array of QuestionModel ([QuestionModel]) instead of [[String]].
Now, you could write your newQ function as:
func newQ() {
let queestion1 = shuffledArray[0]
qlabel.text = question1.content
button1.setTitle(question1.answer1, for: .normal)
button2.setTitle(question1.answer2, for: .normal)
button3.setTitle(question1.answer3, for: .normal)
button4.setTitle(question1.answer4, for: .normal)
}
Following the above approach would be more expressive and easy to work with.
Related
I'm having issues with appending an optional value to an array in Swift. The view I'm writing is for the creation of a routine for the gym. However my Routine object is not being instantiated as it should be.
I have experience with other programming languages but I am fairly new to Swift, and optionals.
My ViewController contains an optional variable:
var routine: Routine?
Where the Routine class contains:
name: String
exerciseList: [String]()
numOfSets: [Int]()
When I am preparing it to send the newly created routine to my other ViewController, I take the values from user input to edit the fields of the object.
let name = routineName.text ?? ""
let numberOne = Int(numOfSetsOne.text ?? "0") //numOfSetsOne is a text label
routine?.exerciseList.append(selectedExerciseOne!) //Haven't tested to see if this works yet
routine?.numOfSets[0] = numberOne! //This line is not working
routine = Routine(name: name)
To try a little debugging I put print statements on either side of the line like so:
print ("numberOne Value: \(numberOne!)")
routine?.numOfSets[0] = numberOne!
print ("numOfSets[0] Value: \(routine?.numOfSets[0])")
I expected the output from the second print statement to be identical to the first. However the terminal output:
numberOne Value: 3
numOfSets[0] Value: nil
Does anyone know what has gone wrong here?
Thanks
You have declared a property that may contain a Routine, but you have not assigned an instance of Routine to that property before trying to use it.
This means that, for example,
routine?.numSets[0] = numberOne!
doesn't do anything - routine is nil and so the statement is skipped.
You should create an appropriate init function for your Routine class and use that to create a new Routine and assign it to routine
For example:
class Routine {
var name: String
var exerciseList = [String]()
var numberOfSets = [Int]()
init(named: String) {
self.name = named
}
}
Then you can say
let name = routineName.text ?? ""
let numberOne = Int(numOfSetsOne.text ?? "0")
self.routine = Routine(named: name)
self.routine?.numberOfSets.append(numberOne!)
Coordinating related arrays can get a bit messy, so I would use a single array:
struct ExerciseSet {
let exerciseName: String
let sets: Int
}
class Routine {
var name: String
var exerciseList = [ExerciseSet]()
init(named: String) {
self.name = named
}
}
Your Routine is not initialised before its being assigned value
try
let name = routineName.text ?? ""
let numberOne = Int(numOfSetsOne.text ?? "0")
routine = Routine(name: name)
routine?.exerciseList.append(selectedExerciseOne!)
routine?.numOfSets[0] = numberOne!
Sorry if the title is rather confusing, but I'm curious to know the difference between these two lines:
var title = String()
var title: String
Is one being initialized and one only be declared? Which is more correct?
For example, if I have a struct should I use one of the other?
So the reason I ask this is because I'm learning about how to grab some JSON from a url and then display it in my app. One of the new ways of doing so is using Decodable. So, I have a struct in a model class like so:
struct Videos: Decodable {
var title = String()
var number_of_views : Int
var thumbnail_image_name: String
var channel: Channel
var duration: Int
}
In another class I have this:
URLSession.shared.dataTask(with: url){(data,response,error) in
if(error != nil){
print(error!)
return
}
guard let data = data else { return }
do{
self.Videos2 = try JSONDecoder().decode([Videos].self, from: data)
//self.collectionView?.reloadData()
}catch let jsonErr{
print(jsonErr)
}
}.resume()
So, should I declare or initialize the variables in my struct? I'm assuming I should just declare them like so:
var title: String?
Would that be the correct syntax in my struct?
UPDATE:
I understand this question was more broad then I originally proposed it to be. I'm sorry about that, but thank you so much for all your great answers that clarified a lot up for me.
The difference is that : defines the type of your variable, whereas = assigns an actual value to the variable.
So:
var title = String()
This calls the initializer of the String type, creating a new String instance. It then assigns this value to title. The type of title is inferred to be String because you're assigning an object of type String to it; however, you could also write this line explicitly as:
var title: String = String()
This would mean you are declaring a title variable of type String, and assigning a new String to it.
var title: String
This simply says you're defining a variable of type String. However, you are not assigning a value to it. You will need to assign something to this variable before you use it, or you will get a compile error (and if this is a property rather than just a variable, you'll need to assign it before you get to the end of your type's init() method, unless it's optional with ? after it, in which case it gets implicitly initialized to nil).
EDIT: For your example, I'd probably declare all the variables using let and :, assuming that your JSON provides values for all of those properties. The initializer generated by Decodable should then set all the properties when you create the object. So, something like:
struct Videos: Decodable {
let title: String
let number_of_views : Int
let thumbnail_image_name: String
let channel: Int
let duration: Int
}
This initializes a value
var title = String()
This declares a value but does not initialize it
var title: String
If you attempt to use the latter, such as print(title), you will get a compiler error stating Variable 'title' used before being initialized
It does not matter whether the value is a class or a struct.
The = operator is the assignment operator, it assigns a value to the object on the left of the =
Typically, class or struct properties are declared but not initialized until the init() is called. A simple class might be
class MyClass {
let myProperty: String
init(aString: String) {
self.myProperty = aString
}
}
Whereas inside the scope of a function you may declare a local variable that only lives inside the scope of the function.
func doSomethingToAString(aString: String) -> String {
let extraString = "Something"
let amendedString = aString + extraString
return amendedString
}
In your specific example, the struct synthesizes an initializer that will allow you to initialize the struct with all the values needed to fill your properties. The initializer generated by Decodable should then set all the properties when you create a Videos struct, you will do it something like:
let aVideos = Videos(title: "My Title", number_of_views: 0, thumbnail_image_name: "ImageName", channel: Channel(), duration: 10)
Is one being initialized and one only be declared?
Yes, meaning that the declared cannot be used. If you tried to set a value for it, you would get a compile-time error:
variable 'title' passed by reference before being initialized
Which is more correct?
There is no rule of thumb to determine which is more correct, that would be depends on is there a need to initialize title directly.
On another hand, when it comes to declare properties for a class, saying var title = String() means that you are give title an initial value ("") which means that you are able to create an instance of this class directly, example:
class Foo {
var title = String()
}
let myFoo = Foo()
However, if title declared as var title: String, you will have to implement the init for Foo:
class Foo {
var title: String
init(title: String) {
self.title = title
}
}
let myFoo = Foo(title: "")
Also, you have an option to declare it as lazy:
lazy var title = String()
which means:
A lazy stored property is a property whose initial value is not
calculated until the first time it is used. You indicate a lazy stored
property by writing the lazy modifier before its declaration.
Properties - Lazy Stored Properties
I'm getting the error message Argument labels '(of:)' do not match any available overloads. Below is the code I'm using.
let prefs = UserDefaults.standard
var id: String!
if var array = prefs.string(forKey: "myArray"){
if let index = array.index(of: id) {
array.remove(at: index)
prefs.setValue(array, forKey: "myArray")
}
}
I've seen a lot of answers on Stack Overflow with very similar code to that. So I'm not quite sure why this wouldn't be working.
Basically I'm just trying to remove the element in the array that = id then set that new array to the user defaults.
Update
Just updated the code above to show how array is getting defined. id is a string that is defined in a separate section.
By accessing prefs.string(forKey: "myArray"), you are getting a String, not an array of strings. You should use this:
if var prefs.array(forKey: "myArray") as? [String] { }
or
if var prefs.value(forKey: "myArray") as? [String] { }
Make sure to not forget putting as! [String], because the first method returns [Any], an which can contain objects of any type, not specifically String. Then your error should be solved, because index(of: ) can only be used on Arrays of specified types.
Hope it helps!
Just make an alt + Click on an "array" variable to make sure it is of type Array ([String]), not a String. To apply .index(of:) method it must be an array.
Like this:
String does not have a method .index(of:). That's what the error is pointing at. And sure make a cast to [String]? if it fits.
Hello I have a for in loop where elements is the variable being changed and in this case "elements" is a string but there is a corresponding variable out side of the for in loop that has the same name as the string called elements. So what I mean is out side there is a Var time = [some,text,words] and theres a for in loop that calls a STRING named "time" and I would like to know how to convert the string in the for in loop into the variable by some how taking off the "'s (not that simple I know) without specifically saying "time"(the variable) but instead converting the "elements"(which is the string 'time') string into the variable. I hope I was clear enough if I'm not making sense I'll try again.
You cannot refer to local variables dynamically by their names in Swift. This would break a lot of compiler optimizations as well as type safety if you could.
You can refer to object properties by their names if the class conforms to key-value coding. For example:
class X : NSObject {
let time = ["some", "text", "words"]
func readWordsFromProp(name: String) -> String {
guard let list = self.valueForKey(name) as? [String] else {
return ""
}
var result = ""
for word in list {
result += word
}
return result
}
}
let x = X()
print(x.readWordsFromProp("time"))
In general, there are better ways to do things in Swift using closures that don't rely on fragile name-matching. But KVC can be a very powerful tool
I'm new to Swift and taking a class to learn iOS programming. I find myself stumped on how to search in an array of dictionaries for a string value and dump the string value into an array. This is taken from my Xcode playground.
I'm trying to figure out how to:
1) search through an array of dictionaries
2) dump the results of the search into an array (which I've created)
These are the character dictionaries.
let worf = [
"name": "Worf",
"rank": "lieutenant",
"information": "son of Mogh, slayer of Gowron",
"favorite drink": "prune juice",
"quote" : "Today is a good day to die."]
let picard = [
"name": "Jean-Luc Picard",
"rank": "captain",
"information": "Captain of the USS Enterprise",
"favorite drink": "tea, Earl Grey, hot"]
This is an array of the character dictionaries listed above.
let characters = [worf, picard]
This is the function I'm trying to write.
func favoriteDrinksArrayForCharacters(characters:Array<Dictionary<String, String>>) -> Array<String> {
// create an array of Strings to dump in favorite drink strings
var favoriteDrinkArray = [String]()
for character in characters {
// look up favorite drink
// add favorite drink to favoriteDrinkArray
}
return favoriteDrinkArray
}
let favoriteDrinks = favoriteDrinksArrayForCharacters(characters)
favoriteDrinks
I would be grateful for any assistance on how to move forward on this. I've dug around for examples, but I'm coming up short finding one that's applicable to what I'm trying to do here.
Inside the loop, you need to fetch the "favorite drink" entry from the dictionary, and append it to the array:
for character in characters {
if let drink = character["favorite drink"] {
favoriteDrinkArray.append(drink)
}
}
Note, the if let drink = guards against the possibility there is no such entry in the array – if there isn't, you get a nil back, and the if is checking for that, only adding the entry if it's not nil.
You might sometimes see people skip the if let part and instead just write let drink = character["favorite drink"]!, with an exclamation mark on the end. Do not do this. This is known as "force unwrapping" an optional, and if there is ever not a valid value returned from the dictionary, your program will crash.
The behavior with the first example is, if there is no drink you don't add it to the array. But this might not be what you want since you may be expecting a 1-to-1 correspondence between entries in the character array and entries in the drinks array.
If that's the case, and you perhaps want an empty string, you could do this instead:
func favoriteDrinksArrayForCharacters(characters: [[String:String]]) -> [String] {
return characters.map { character in
character["favorite drink"] ?? ""
}
}
The .map means: run through every entry in characters, and put the result of running this expression in a new array (which you then return).
The ?? means: if you get back a nil from the left-hand side, replace it with the value on the right-hand side.
Airspeed Velocity's answer is very comprehensive and provides a solution that works. A more compact way of achieving the same result is using the filter and map methods of swift arrays:
func favoriteDrinksArrayForCharacters(characters:Array<Dictionary<String, String>>) -> Array<String> {
// create an array of Strings to dump in favorite drink strings
return characters.filter { $0["favorite drink"] != nil }.map { $0["favorite drink"]! }
}
The filter takes a closure returning a boolean, which states whether an element must be included or not - in our case, it checks for the existence of an element for key "favorite drink". This method returns the array of dictionaries satisfying that condition.
The second step uses the map method to transform each dictionary into the value corresponding to the "favorite drink" key - taking into account that a dictionary lookup always returns an optional (to account for missing key), and that the filter has already excluded all dictionaries not having a value for that key, it's safe to apply the forced unwrapping operator ! to return a non optional string.
The combined result is an array of strings - copied from my playground:
["prune juice", "tea, Earl Grey, hot"]
let drinks = characters.map({$0["favorite drink"]}) // [Optional("prune juice"), Optional("tea, Earl Grey, hot")]
or
let drinks = characters.filter({$0["favorite drink"] != nil}).map({$0["favorite drink"]!}) // [prune juice, tea, Earl Grey, hot]
It may help you
var customerNameDict = ["firstName":"karthi","LastName":"alagu","MiddleName":"prabhu"];
var clientNameDict = ["firstName":"Selva","LastName":"kumar","MiddleName":"m"];
var employeeNameDict = ["firstName":"karthi","LastName":"prabhu","MiddleName":"kp"];
var attributeValue = "karthi";
var arrNames:Array = [customerNameDict,clientNameDict,employeeNameDict];
var namePredicate = NSPredicate(format: "firstName like %#",attributeValue);
let filteredArray = arrNames.filter { namePredicate.evaluateWithObject($0) };
println("names = ,\(filteredArray)");
Use the following code to search from NSArray of dictionaries whose keys are ID and Name.
var txtVal:NSString
let path = NSBundle.mainBundle().pathForResource(plistName, ofType: "plist")
var list = NSArray(contentsOfFile: path!) as [[String:String]]
var namePredicate = NSPredicate(format: "ID like %#", String(forId));
let filteredArray = list.filter { namePredicate!.evaluateWithObject($0) };
if filteredArray.count != 0
{
let value = filteredArray[0] as NSDictionary
txtVal = value.objectForKey("Name") as String
}
i have array of customer ,each customer having name,phone number and other stubs .so i used the below code to search by phone number in the array of dictionary in search bar
for index in self.customerArray
{
var string = index.valueForKey("phone")
if let phoneNumber = index.valueForKey("phone") as? String {
string = phoneNumber
}
else
{
string = ""
}
if string!.localizedCaseInsensitiveContainsString(searchText) {
filtered.addObject(index)
searchActive = true;
}
}