Turn simple variable into an array (Swift) - ios

I had this variable in a my viewController var category : QCategoryy?
and I decided to turn it into an array so I created this: var categories: [QCategoryy?]? = []
but after that i get all errors like this
"Value of type '[QCategoryy?]' has no member 'name'"
in this line self.title = categories?.nameand other lines like that.
Why i get these errors and how can i solve it?
First to turn the var into an array all worked well.
This is the class of QCategoryy
struct QCategoryy {
var name:String
var isSelected = false
init(name:String) {
self.name = name
}
}
extension QCategoryy: ExpressibleByStringLiteral {
init(stringLiteral value: String) {
self.name = value
}
init(unicodeScalarLiteral value: String) {
self.init(name: value)
}
init(extendedGraphemeClusterLiteral value: String) {
self.init(name: value)
}
}

Very simple, you have converted that variable to array. So, first of all you have to give index of the array to invoke the value of object at specific index.
like given below
for index in 0..<10 {
if let category = categories[index] {
self.title = category.name
}
}
conditional binding will prevent from crashing app if categories[0] is nil.
UPDATE
To prevent array index out of bound in loop. You can use below example.
for category in categories {
title = category.name
}

Here categories is an array. So do something like this
self.title = categories?[0]?.name

categories in now an array. So you'll have to refer to some object of it whose name you name you're referring to.
More like, categories?[index]?.name

Or you can use forEach:
categories?.forEach{ value in
self.title = value?.name
}

Related

Return a single String from a dictionary using uniqueKeysWithValues

Goal of the code:
To assign a struct dictionary with Strings as Keys and String Arrays as values to a variable and then pull one (can be at random) specific String key value in the String Array and return that one String element in the underlying String Array so that it can be used elsewhere (potentially assigned to a label.text)
Essentially (please reference code below), I want to access one value at random in myDictionary using a specific key ("keyOne"), and pull, let's say, "Value2" then return only the string "Value2" from the underlying String Array associated with "keyOne" using indexing.
Errors are in the code below.
The issue I'm thinking is that I haven't figured out how to turn my final var Testing = dict["keyOne"] into an Int compatible index... if it was an index, the code would pull an Int value and the corresponding String from the three Strings in the underlying value array (due to the three String values associated with "keyOne").
Also, variableView() just inherits the datasource from several other containers, but the var dataSource : Structure? is the main reference, so that is what I included.
Code so far:
let myDictionary = [Structure(name: "keyOne", text: ["Value1", "Value2", "Value3"]), Structure(name: "keyTwo", text: ["Value4", "Value5", "Value6"])]
lazy var dict = Dictionary(uniqueKeysWithValues: myDictionary.lazy.map { ($0.name, $0.text) })
struct Structure: Hashable {
var name: String
var text: [String]
init(name: String, text: [String]){
self.name = name
self.text = text
}
}
func variable(at index: Int) -> variableView {
let variable = variableView()
var Testing = dict["keyOne"]
variable.dataSource = Testing![index] <- Cannot assign value of type 'String' to type 'structure'
return variable
var dataSource : Structure? {
didSet {
label.text = "This is a test"
} else {
// n/a
}
}
Please note that the error message is above in the code for variable.dataSource = Testing![index].
I am also suspecting that my issue lies in the "looping" logic of how I am assigning a variable with a struct, to a datasource which references that same struct.
Any help is appreciated as I have been stuck on this for legitimately a week (I truly have exhausted every single StackOverflow answer/question pair I could find).
THANK YOU!
EDIT:
I found this documentation to assist me greatly with this, and I recommend anyone with a similar question as mine to reference this: https://swift.org/blog/dictionary-and-set-improvements/
Given the question and the discussion in the comments I would add a mutating func to the struct that removes and returns a random string
mutating func pullText() -> String? {
guard let index = text.indices.randomElement() else {
return nil
}
return text.remove(at: index)
}
Example
if let index = myDictionary.firstIndex(where: { $0.name == "keyOne" }),
let text = myDictionary[index].pullText() {
someLabel.text = text
}
Here is another example based on the code in the question
Assuming VariableView looks something like this
struct VariableView: View {
var dataSource : Structure?
var word: String?
var body: some View {
Text(word ?? "")
}
}
Then the func variable can be changed to
func variable() -> VariableView {
var variable = VariableView()
if let index = dict.firstIndex(where: { $0.name == "keyOne" }) {
variable.dataSource = dict[index]
variable.word = dict[index].pullText()
}
return variable
}

Swift: Merging 2 or more elements custom objects in array based on unique value

Below is my custom object class.
class UserGroups: NSObject {
let groupName: String
let users: [CheckIn]?
init(json:JSON) {
self.groupName = json[Constants.Models.UserGroups.groupName].stringValue
self.users = UserGroups.getUserGroupsList(jsonArray: json[Constants.Models.UserGroups.users].arrayValue)
}
class func getUserGroupsList(jsonArray: [JSON]) -> [CheckIn]{
return jsonArray.flatMap({ (jsonItem: JSON) -> CheckIn in
return CheckIn(json: jsonItem)
})
}
}
I've an array of above custom objects. How can I combine 2 or more custom objects into a single object by merging users of every object having same groupName.
Below is my CheckIn Model:
class CheckIn: NSObject {
let id: String
let firstName: String
let lastName: String
let latitude: String
let longitude: String
let hint: String
init(json: JSON) {
self.id = json[Constants.Models.CheckIn.id].stringValue
self.firstName = json[Constants.Models.CheckIn.firstName].stringValue
self.lastName = json[Constants.Models.CheckIn.lastName].stringValue
self.hint = json[Constants.Models.CheckIn.hint].stringValue
self.latitude = json["location"][Constants.Models.CheckIn.latitude].stringValue
self.longitude = json["location"][Constants.Models.CheckIn.longitude].stringValue
}
}
id field is not unique in CheckIn.
Here's a slightly simplified example that shows how to combine groups that have the same group name.
Here is the UserGroup class. users is now a variable (var) because we will be adding elements to groups to combine them.
class UserGroups: NSObject {
let groupName: String
var users: [String]?
init(groupName: String, users: [String]?) {
self.groupName = groupName
self.users = users
}
}
Here are three groups, two of the share the same group name, Blues.
let group1 = UserGroups(groupName: "Blues", users: ["Tom", "Huck", "Jim"])
let group2 = UserGroups(groupName: "Reds", users: ["Jo", "Ben", "Tommy"])
let group3 = UserGroups(groupName: "Blues", users: ["Polly", "Watson", "Douglas"])
Next, we'll put all the groups in an array.
let allGroups = [group1, group2, group3]
Here, we use Swift's reduce function to allow us to reduce the array to only groups with unique group names.
let compacted = allGroups.reduce([UserGroups](), { partialResult, group in
var dupe = partialResult.filter {$0.groupName == group.groupName }.first
if let dupeGroup = dupe {
dupeGroup.users?.append(contentsOf: group.users ?? [])
return partialResult
} else {
var newPartialResult = partialResult
newPartialResult.append(group)
return newPartialResult
}
})
The array is now reduced to unique groups, we print out all the groups and their users with the help of Swift's map function.
print(compacted.map { $0.users })
// Prints [
Optional(["Tom", "Huck", "Jim", "Polly", "Watson", "Douglas"]),
Optional(["Jo", "Ben", "Tommy"])
]
The Solution
You did not include the CheckIn model, but I will assume that it has some sort of an id field unique to each user. We will use this to make the object Hashable:
// Add this to your file outside of the UserGroups class
extension CheckIn: Hashable {
var hashValue: Int { return self.id }
}
Making it Hashable allows you to convert the Array to a Set, which does not allow duplicates and will remove them in a very efficient way.
// Change getUserGroupsList as follows
class func getUserGroupsList(jsonArray: [JSON]) -> [CheckIn] {
return Array(Set(jsonArray.flatMap({ (jsonItem: JSON) -> CheckIn in
return CheckIn(json: jsonItem)
})))
}
Optional Considerations
As an aside, in case you're coming from another language, Swift gives you nice type inference and default names for closure arguments ($0 is the first argument). You can probably make the code a little less verbose, but it's a matter of taste which is preferred.
class func getUserGroupsList(jsonArray: [JSON]) -> [CheckIn] {
return Array(Set(jsonArray.flatMap { CheckIn(json: $0) }))
}
Also consider whether you really want the return value to be an array. If you want the list to always have unique users, it is a bit more efficient to use a Set as your return type and forgo the conversion back to an Array like this:
class func getUserGroupsList(jsonArray: [JSON]) -> Set<CheckIn> {
return Set(jsonArray.flatMap { CheckIn(json: $0) })
}
Finally, consider whether you really need the users property to be optional. With sequence types, it is often sufficient to use an empty sequence to denote absence of a value. Depending on your situation, this may simplify your code. The final version looks like this:
class UserGroups: NSObject {
let groupName: String
let users: Set<CheckIn>
init(json:JSON) {
self.groupName = json[Constants.Models.UserGroups.groupName].stringValue
self.users = UserGroups.getUserGroupsList(jsonArray: json[Constants.Models.UserGroups.users].arrayValue)
}
class func getUserGroupsList(jsonArray: [JSON]) -> Set<CheckIn> {
return Set(jsonArray.flatMap { CheckIn(json: $0) })
}
}
Maintaining Order
The caveat is that Set does not maintain the order of the items. If the order of the groups does matter, we can use this solution instead:
class func getUserGroupsList(jsonArray: [JSON]) -> [CheckIn] {
var encountered: Set<CheckIn> = []
return jsonArray.flatMap { CheckIn(json: $0) }.filter { encountered.update(with: $0) == nil }
}
In this version, we still use a set, but only to maintain a set of items we've encountered already. The update method on a set returns the same value if it's already in the set or returns nil if it's being inserted for the first time. We use that to filter our array to those items being encountered for the first time while adding them to the set of encountered items to filter them out when they are subsequently encountered again.

SwiftyJSON error

I have a class with init a build method that i want to use to create instances of class.
Code is the following
class Article {
let id:Int
let title:String
let subtitle:String
let editor1:String
let mainImage:NSData
init(id:Int, title:String, subtitle:String, editor1:String, mainImage:NSData) {
self.id = id
self.title = title
self.subtitle = subtitle
self.editor1 = editor1
self.mainImage = mainImage
}
class func build(json:JSON) -> Article {
id = Int(json["id"].string),
title = json["title"].string,
subtitle = json["subtitle"].string,
editor1 = json["editor1"].string,
mainImage = json["images"]["main"].rawData() {
return Article(
id: id,
title: title,
subtitle: subtitle,
editor1: editor1,
mainImage: mainImage)
}
}
}
But i have errors
What am I doing wrong ?
SwiftyJSON's .rawData() is an Optional getter.
So I guess what you wanted to do is use if let:
class func build(json:JSON) -> Article? {
id = Int(json["id"].string)
title = json["title"].string
subtitle = json["subtitle"].string
editor1 = json["editor1"].string
if let mainImage = json["images"]["main"].rawData() {
return Article(
id: id,
title: title,
subtitle: subtitle,
editor1: editor1,
mainImage: mainImage)
} else {
// ...
return nil
}
}
Also it looks like that you copied/pasted the parameters from your Article initializer to declare them earlier in the function but you forgot to get rid of the commas at the end of the lines.
Update
Your problem is that your class properties are immutable (declared with let) but inside this function you are trying to change their values:
id = Int(json["id"].string)
This is interpreted as
self.id = Int(json["id"].string)
And you can't change the value of self.id because it is immutable.
Solutions:
1- Make the properties mutable by using var instead of let. Example:
var id:Int
var title:String
var subtitle:String
var editor1:String
var mainImage:NSData
or
2- Do not replace the properties in the function since you're going to init with your new object anyway. Example:
class func build(json:JSON) -> Article? {
if let img = json["images"]["main"].rawData() {
return Article(
id: Int(json["id"].string),
title: json["title"].string,
subtitle: json["subtitle"].string,
editor1: json["editor1"].string,
mainImage: img)
} else {
// ...
return nil
}
}
Update 2
If the compiler complains about "not marked with try", do the "if let" with "try?":
if let img = try? json["images"]["main"].rawData() {
Explanation: SwiftyJSON may have changed this method without updating the documentation yet (or I didn't find it). It previously returned an Optional and now seems to "throw" instead. Using "try?" lets you make it an Optional again.
Couple things look off.
You should be returning Article? since there is a chance you're going to be returning nil.
You've got a number of extraneous commas after things like id = Int(json["id.... Get rid of them.
That closure seems unnecessary. Simply check for the valid JSON first, if its bad return nil, otherwise build it up and return the article.
You have ,s where they shouldn't be. Try using this:
class func build(json:JSON) -> Article {
id = Int(json["id"].string)
title = json["title"].string
subtitle = json["subtitle"].string
editor1 = json["editor1"].string
mainImage = json["images"]["main"].rawData() {
return Article(
id: id,
title: title,
subtitle: subtitle,
editor1: editor1,
mainImage: mainImage)
}
}

Calling Swift closure with parameters

I am new to Swift and trying to write my first function that calls a closure that the user passes in. I am having trouble calling my function (which I named fun). I also was unable to find any examples of this online. I just want to call my closure (I am unsure what to pass to it?) and then make a decision based on the boolean result? This seems very easy yet I am not sure.
The goal of the method is to remove duplicates in an array based on the users specifications. In my case I may want to pass in a certain class and an array of it and then remove all classes that have the same name property (ie a name string that matches).
extension Array{
func removeDuplicates<T: Comparable>(fun: (elem: T, arr: [T]) -> Bool) -> [T]
{
var array = [T]()
for element in self
{
if fun(elem: T(), arr: [T])
{
println("hello")
}
}
return array
}
}
This is a slight generalization of Does there exist within Swift's API an easy way to remove duplicate elements from an array? and might be what you are
looking for:
extension Array {
func withoutDuplicates<U : Hashable>(attribute : T -> U) -> [T] {
var result : [T] = []
var seen : Set<U> = Set()
for elem in self {
let value = attribute(elem)
if !seen.contains(value) {
result.append(elem)
seen.insert(value)
}
}
return result
}
}
The attribute closure is applied to each array element, and
a Set is used to keep track of values that occurred already.
Therefore the value type U is required to be Hashable (which
is the case for strings).
Example:
struct Person : Printable {
let firstName : String
let lastName : String
var description: String {
return "\(firstName) \(lastName)"
}
}
let array = [
Person(firstName: "John", lastName: "Doe"),
Person(firstName: "Joe", lastName: "Miller"),
Person(firstName: "Jane", lastName: "Doe")
]
let result = array.withoutDuplicates( { $0.lastName } )
println(result)
// [John Doe, Joe Miller]
An alternative implementation is
func withoutDuplicates<U : Hashable>(attribute : T -> U) -> [T] {
var seen : [U : Bool] = [:]
return self.filter { seen.updateValue(true, forKey: attribute($0)) == nil }
}
which utilizes the fact that the updateValue() method of Dictionary
returns the previous value for the key, and in particular returns nil if the key was not set previously. This is also just a slight generalization of #rintaro's answer to iOS Swift: Filter array to unique items.

Construct typed dictionary using swift

I would like to create a typed map (Dictionary) class to meet the following requirements:
func testMap() {
var map = ActivitiesMap()
var activity = Activity()
activity.title = "Activity 1"
activity.uuid = "asdf1234"
map[activity.uuid] = activity
for (key, mapActivity) in map {
logger.debug("ACTIVITY MAP: \(key)=\(mapActivity)")
}
}
In short, I want this class to both be a dictionary such that it can be used in the for loop, however I want to ensure the keys are strings and the values are Activity objects.
I tried many variations of inheriting from Dictionary or typing the class, but so far it's resulted in multiple errors.
EDIT:
I don't think a simple generic dictionary will work, such as String:Activity. I want to have extra methods in the ActivityMap class, such as getAllActivitiesBetweenDates().
I need an actual class definition, not a generic dictionary expression.
You can make it looks like dictionary by implement subscript operator
And conform to Sequence protocol to support for-in loop
struct ActivitiesMap : Sequence {
var map = [String:Activity]()
subscript(key: String) -> Activity? {
get {
return map[key]
}
set(newValue) {
map[key] = newValue
}
}
func generate() -> GeneratorOf<(String, Activity)> {
var gen = map.generate()
return GeneratorOf() {
return gen.next()
}
}
// I can't find out type of map.generator() now, if you know it, you can do
//func generate() -> /*type of map.generator()*/ {
// return map.generate();
//}
}
This works for me. Not sure what is in your ActivitiesMap class, but just typed a Dictionary
class Activity{
var title:String = "";
var uuid: String = "";
}
func testMap() {
//var map = ActivitiesMap()
var map: Dictionary< String, Activity> = Dictionary< String, Activity>();
var activity = Activity()
activity.title = "Activity 1"
activity.uuid = "asdf1234"
map[activity.uuid] = activity
for (key, mapActivity) in map {
println("ACTIVITY MAP: \(key)=\(mapActivity)")
}
}
testMap();
This is my output:
ACTIVITY MAP: asdf1234=C11lldb_expr_08Activity (has 2 children)
class Activity {
var title=""
var id=""
init(id:String, title:String) { self.id=id; self.title = title }
}
var activities = [String:Activity]()
let a1 = Activity(id:"a1", title:"title1")
let a2 = Activity(id:"a2", title:"title2")
let a3 = Activity(id:"a3", title:"title3")
activities[a1.id] = a1
activities[a2.id] = a2
activities[a3.id] = a3
for (id,activity) in activities {
println("id: \(id) - \(activity.title)")
}
should print
id: a2 - title2
id: a3 - title3
id: a1 - title1
(key order not guaranteed to be the same)
You can use typealias keyword to define nice name of any type.
Here is how it can be used for your code:
class Activity { /* your code */ }
typealias ActivityMap = Dictionary<String, Activity>
var activityDict = ActivityMap()
And to support custom functions you can write an extension, example bellow:
extension Dictionary {
func getAllActivitiesBetweenDates(fromDate:NSDate, toDate:NSDate) -> Array<Activity>
// your code
return []
}
}
Usage:
let matchedActivities = activityDict.getAllActivitiesBetweenDates(/*date*/, /*date*/)

Resources