Swift initialise empty array to store different structs later - arrays

I have a couple of different types of structs (Promo & Event). I'd like to create an empty array which gets populated with an array of each type depending on the outcome of an if statement.
So something like this:
var dataArray:[Any] = [] // see options I've tried below
if myTest == true {
dataArray = [Promo, Promo, Promo]
} else {
dataArray = [Event, Event, Event]
}
I have tried using:
1. var dataArray: [Any] = []
2. var dataArray: [AnyObject] = []
3. var dataArray: [Any] = [Any]()
4. var dataArray: [AnyObject] = [AnyObject]()
but when I try to store an array of Promo Structs in dataArray I get an error Cannot assign value of type '[Promo]' to type '[Any]' etc.
So, how do I initialise an array so that it can store a variety of (unknown) Structs. Or how do I modify my Structs to enable them to be stored in an array?
I'm really struggling to see what I'm doing wrong so any pointers would be v. helpful.
Here are my Structs:
Promo.swift
import Foundation
struct Promo {
// initialise the stored properties for use later
let promoId : Int
let date : NSDate
let title: String
let body: String
let busName : String
let busId : Int
let categoryId: Int
let featured: Bool
// a universal init() method which has optional parameters
init(promoId: Int,
date: NSDate,
title: String,
body: String,
busName: String,
busId: Int,
categoryId: Int,
featured: Bool
){
self.promoId = promoId
self.date = date
self.title = title
self.body = body
self.busName = busName
self.busId = busId
self.categoryId = categoryId
self.featured = featured
}
}
// allow us to compare structs
extension Promo: Equatable {}
func ==(lhs: Promo, rhs: Promo) -> Bool {
return lhs.promoId == rhs.promoId
&& lhs.date == rhs.date
&& lhs.title == rhs.title
&& lhs.body == rhs.body
&& lhs.busName == rhs.busName
&& lhs.busId == rhs.busId
&& lhs.categoryId == rhs.categoryId
&& lhs.featured == rhs.featured
}
Event.swift
import Foundation
struct Event {
// initialise the stored properties for use later
let eventId : Int
let date : NSDate
let title: String
let body: String
let busName : String
let busId : Int
let categoryId: Int
// a universal init() method which has optional parameters
init(eventId: Int,
date: NSDate,
title: String,
body: String,
busName: String,
busId: Int,
categoryId: Int
){
self.eventId = eventId
self.date = date
self.title = title
self.body = body
self.busName = busName
self.busId = busId
self.categoryId = categoryId
}
}

This may not be exactly what you intended, but you can make this a bit cleaner by using classes instead of structs. It appears that a 'Promo' is just an 'Event' with one extra data member (featured)... If that's the case, then you can rename the Promo.promoId field Promo.eventId, and then make it a subclass of Event. Like this:
class Promo : Event {
let featured: Bool
// a universal init() method which has optional parameters
init(eventId: Int,
date: NSDate,
title: String,
body: String,
busName: String,
busId: Int,
categoryId: Int,
featured: Bool
){
self.featured = featured
super.init(eventId: eventId, date: date, title: title, body: body, busName: busName, busId: busId, categoryId: categoryId)
}
}
Then just create the data array like this:
var dataArray = [Event]()
if myTest == true {
dataArray = [promo1, promo2, promo3]
} else {
dataArray = [event1, event2, event3]
}
To use the featured member for a Promo you'll still need to cast like this:
if let thisPromo = dataArray[0] as? Promo {
print(thisPromo.featured)
}

If you are trying to assign to dataArray from [Promo] or [Event] arrays, you could map:
var dataArray:[Any] = []
var promoArray:[Promo] = [Promo(), Promo(), Promo()]
var eventArray:[Event] = [Event(), Event(),Event()]
if myTest == true {
dataArray = promoArray.map { $0 as Any }
} else {
dataArray = eventArray.map { $0 as Any }
}
Or create new Any arrays:
if myTest == true {
dataArray = Array<Any>(arrayLiteral: promoArray)
} else {
dataArray = Array<Any>(arrayLiteral: eventArray)
}

Related

How to append an array inside an array?

I have an array of working hours, and within that array, there is another array of shifts. I would like to remove all instances in the shifts array where weekday is equal to "Monday"
struct Shift {
let id: String = UUID().uuidString
var weekday: String
var startTime: String
var endTime: String
var error: Bool
}
struct WorkingHours: Identifiable {
let id: String = UUID().uuidString
var availability: [Shift]
}
class AvailabilityManager: ObservableObject {
#Published var workingHours: [WorkingHours] = []
}
In my view:
#EnvironmentObject var availabilityManager: AvailabilityManager
self.availabilityManager.workingHours.removeAll(where: {$0.availability.weekday == "Monday"})
However, it says: "Value of type '[Shift]' has no member 'weekday'"
Any help is appreciated :)
Change
self.availabilityManager.workingHours.removeAll(where: {$0.availability.weekday == "Monday"})
To
self.availabilityManager.workingHours.removeAll(where: {$0.availability.contains(where: {$0.weekday == "Monday"})})
More shorthand method
self.availabilityManager.workingHours.removeAll { $0.availability.contains { $0.weekday == "Monday" } }
Add the following function to WorkingHours
mutating func removeShift(weekDay: String) {
availability = availability.filter { $0.weekday != weekDay }
}
and call it like
workingHour.removeShift(weekDay: "Monday")
If you have an array of WorkinHours you can call the method using map for instance
workingHoursArray = workingHoursArray.map {
var workingHours = $0
workingHours.removeShift(weekDay: "Monday")
return workingHours
}

SwiftUI: How do I display multidimensional array as sections in List?

I have the following object as a var events = [[EventLog]] and I need to loop through each inner-array and display them in a section. For example, events[0] might have events[0][0].id events[0][1].id and events[0][2].id as values and events[1] may only have 1 element. This is the object that I've built for reference.
Model Object
class EventLog: Identifiable {
let id: UUID
let ipAddress: String
let date: String
let getMethod: String
let statusCode: String
let secondStatusCode: String
let versionInfo: String
init(ipAddress: String, date: String, getMethod: String, statusCode: String, secondStatusCode: String, versionInfo: String ){
self.id = UUID()
self.ipAddress = ipAddress
self.date = date
self.getMethod = getMethod
self.statusCode = statusCode
self.secondStatusCode = secondStatusCode
self.versionInfo = versionInfo
}
}
Attempts At Using
Doing it this way results in this error: Referencing initializer 'init(_:content:)' on 'ForEach' requires that '[EventLog]' conform to 'Identifiable' It should be noted that the multidimensional array is stored on a ViewModel named landingPageVM which is not referenced here for the sake of brevity. It is however an #Published property of the view model.
ForEach(landingPageVM.eventLogs) { logs in
ForEach(logs)) { log in
Text(log.id.description)
}
}
If you can change your model to a struct instead of a class, you'll have an easier time with this (and a lot of other things in SwiftUI):
struct ContentView : View {
#State var eventLogs : [[EventLog]] = []
var body: some View {
List {
ForEach(eventLogs, id: \.self) { logs in
Section(header: Text("\(logs.count)")) {
ForEach(logs) { log in
Text(log.id.description)
}
}
}
}
}
}
struct EventLog: Identifiable, Hashable {
let id: UUID
let ipAddress: String
let date: String
let getMethod: String
let statusCode: String
let secondStatusCode: String
let versionInfo: String
}
If you're stuck using a class for some reason, you can still get this behavior by doing something like:
extension EventLog : Hashable, Equatable {
func hash(into hasher: inout Hasher) {
hasher.combine(id) //etc
}
static func == (lhs: EventLog, rhs: EventLog) -> Bool {
lhs.id == rhs.id && lhs.ipAddress == rhs.ipAddress //etc
}
}
Details of the hash and == implementations may vary.

Iterate array of dictionary and sort based on key

I have array of dictionary and the values are as follows:
[["-MXpvzmZdbqzrjND8w9F": {
lid = "-MW6eEidZFCLeeZ0uBk1";
message = hi;
timeStamp = 1617960107264;
title = "Sambhar Dosa";
user = 1QSU0c1q8QNrZzmICXGClC0o86E3;
}, "-MXq5NAyrkk4ZcvRFM7T": {
lid = "-MW6eEidZFCLeeZ0uBk1";
message = "how ru?";
timeStamp = 1617962828647;
title = "Sambhar Dosa";
user = 1QSU0c1q8QNrZzmICXGClC0o86E3;
}], ["-MXqa5-pkC28lY_Q_hpZ": {
lid = "-MWwEpHAhIdhN0i5sltB";
message = "hi nice cycle";
timeStamp = 1617971142820;
title = "Cycle for kids";
user = 1QSU0c1q8QNrZzmICXGClC0o86E3;
}]]
Here there are 2 elements in the array. I want to take the last element in both of the array and sort it based on the timestamp value.
How to do it? Please help me.
From comments, it seems that that the type of the array is:
typealias SomeDataArray = [[String: [String: Any]]]
So we're missing type safety from the beginning. The first thing I'd do is define a struct to represent Any in that definition. For now I'll just use a struct as a wrapper for the inner dictionary, using computed properties for all the fields:
struct SomeData
{
let dict: [String: Any]
var lid: String? { dict["lid"] as? String }
var message: String? { dict["message"] as? String }
var timeStamp: Int { dict["timeStamp"] as? Int ?? 0 }
var title: String? { dict["title"] as? String }
// Skipping user, because I have no idea what to make of its type.
}
Really this should be decoded into some real Swift type, but that's a topic for another day.
So now we redo the typealias
typealias SomeDataArray = [[String: SomeData]]
In chat it was explained what within the "last" element for each of these dictionaries can be defined as the one with the largest timeStamp value. Given that this will give this solution (retaining the keys)
let results = testData.map {
dict in dict.map {
($0.key, SomeData(dict: $0.value))
}.sorted { $0.1.timeStamp < $1.1.timeStamp }.last
}.filter { $0 != nil }.map { $0! }.sorted { $0.1.timeStamp < $1.1.timeStamp }
If you want to transform it back the [[String;Any]] you get from Firebase, then it would be this:
let results = testData.map {
dict in dict.map {
($0.key, SomeData(dict: $0.value))
}.sorted { $0.1.timeStamp < $1.1.timeStamp }.last
}.filter { $0 != nil }.map { $0! }.sorted { $0.1.timeStamp < $1.1.timeStamp }
.map { [$0.0: $0.1.dict as Any] }

Set new objects to the array. Error: Cannot use mutating member on immutable value: function call returns immutable value

As far as i can see, it's quite a common mistake. However, I have not found a solution to my version of the problem. I want to add a list of new objects to an already created list based on the index. When I want to add each new Data object to the corresponding Sensor object by append(), I get this error: Cannot use mutating member on immutable value: function call returns immutable value
However, I am unable to deal with this error. How to solve it?
function:
func setStationItemsWithData(addItemList: [AddItem], dataList: [Data]) {
var dataItems = [DataItem]()
var addItems = [AddItem]()
addItems.append(contentsOf: addItemList)
let addItemSensors = addItemList.flatMap{$0.sensors}
dataList.forEach { data in
guard let index = addItemSensors.firstIndex(where: {$0.id == data.id})
else {
print("Failed to find a Data for AddItem \(data.id)")
return
}
let dataItem = DataItem(
values: [ValueItem(
date: data.values.first?.date ?? "",
value: data.values.first?.value.value ?? 0.0)])
dataItems.append(dataItem)
addItems.compactMap{$0.sensors[index].data}.append(dataItem) !!!//here in .append() shows the error
}
AddItem:
class AddItem: Object, Identifiable {
#objc dynamic var id: Int = 0
#objc dynamic var stationId: Int = 0
#objc dynamic var cityName: String = ""
#objc dynamic var addressStreet: String = ""
var added = RealmOptional<Bool>()
let sensors = List<SensorItem>()
convenience init(id: Int, stationId: Int, cityName: String, addressStreet: String, added: RealmOptional<Bool>, sensor: [SensorItem]) {
self.init()
self.id = id
self.stationId = stationId
self.cityName = cityName
self.addressStreet = addressStreet
self.added = added
self.sensors.append(objectsIn: sensor)
}
override class func primaryKey() -> String? {
return "id"
}
}
class SensorItem: Object {
#objc dynamic var id: Int = 0
#objc dynamic var stationId: Int = 0
#objc dynamic var param: ParamItem?
#objc dynamic var data: DataItem?
convenience init(id: Int, stationId: Int, param: ParamItem, data: DataItem) {
self.init()
self.id = id
self.stationId = stationId
self.param = param
self.data = data
}
}
class ParamItem: Object {
#objc dynamic var paramName: String = ""
#objc dynamic var paramFormula: String = ""
#objc dynamic var paramCode: String = ""
#objc dynamic var idParam: Int = 0
convenience init(paramName: String, paramFormula: String, paramCode: String, idParam: Int) {
self.init()
self.paramName = paramName
self.paramFormula = paramFormula
self.paramCode = paramCode
self.idParam = idParam
}
}
class DataItem: Object {
#objc dynamic var id: Int = 0
var values = List<ValueItem>()
convenience init(values: [ValueItem]) {
self.init()
self.values.append(objectsIn: values)
}
}
class ValueItem: Object {
#objc dynamic var date: String? = ""
#objc dynamic var value: Double = 0
convenience init(date: String, value: Double) {
self.init()
self.date = date
self.value = value
}
}

How can I merge 2 dictionaries into one array?

My JSON data look like this image below. Now I wanna merge the value of Shop Type and Promotion into one to use as collection view data. How can I do that?
I just filter the response data from the server like this:
var dataBanDau: [SDFilterModel] = []
var quickData: [SDFilterModel] = []
let filters: [SDFilterModel] = data
self.filterEntries = filters
//let nsarray = NSArray(array: self.filterEntries! , copyItems: true)
// self.filterEntriesStoreConstant = nsarray as! Array
self.dataBanDau = filters
for i in 0..<self.dataBanDau.count {
if self.dataBanDau[i].search_key.count == 0 {
self.quickData.append(self.dataBanDau[i])
}
}
self.quickData = self.quickData.filter {
$0.type != "range"
}
DispatchQueue.main.async {
//Note: Reload TableView
self.quickFilterCollection.reloadData()
completed(true)
}
}
the class SDFilterModel:
class SDFilterModel: DSBaseModel {
var name = String()
var type = String()
var is_expanded = Int()
var search_key = String()
var filterEntries : [SDFilterModel]?
override func copy(with zone: NSZone? = nil) -> Any {
// This is the reason why `init(_ model: GameModel)`
// must be required, because `GameModel` is not `final`.
let copy = SDFilterModel(dict: self.dictionary)
if let arrAttribute = NSArray(array: self.value , copyItems: true) as? [AttributeValueModel] {
copy.value = arrAttribute
}
return copy
}
override init(dict: Dictionary<String, Any>) {
super.init(dict: dict);
value = self.valueParse()
name = dict.getString(forKey: "name")
type = dict.getString(forKey: "type")
search_key = dict.getString(forKey: "search_key")
is_expanded = dict.getInt(forKey: "is_expanded")!
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
var value: [AttributeValueModel] = [];
func valueParse()-> [AttributeValueModel] {
guard let childs = (self.dictionary["value"]) as? [Dictionary<String, AnyObject>]
else { return [] }
var output: [AttributeValueModel] = [];
for aDict in childs {
let item = AttributeValueModel(dict:aDict);
// if type == .Range && item.option_id == "0" {
// item.setRangeOptionID(aValue: item.option_name!)
// }
//
output.append(item);
}
return output;
}
Let be Assume you have let myArray = [1,2,3,4,5,6,7,8]
Now you wanted to square of each and every element in the array,
With for loop you do like this
for item in myArray {
print(item * item)
}
Now assume item = $0
With for map you jus do
myArray.map({ $0 * $0 })
Both will gave same output.
map : Use to do same operation on every element of array.
flatmap : It is used to flattern the array of array.
let myArr = [[1,2,3],[4,5,6,7]]
and you want o/p as [1,2,3,4,5,6,7]
So can get above output with myArr.flatMap({$0})
Now back to your question.
let reqArray = myModel.data.map({ $0.value }).flatMap({ $0 })
First, map gaves you array-of-array of key value but you need a single array, so for that you need to use flatmap.
You can take ref : https://medium.com/#Dougly/higher-order-functions-in-swift-sorted-map-filter-reduce-dff60b5b6adf
Create the models like this
struct Option {
let name: String
let searchKey: String
let id: String
}
struct Model {
let type: String
let name: String
let isExpanded: Bool
let value: [Option]
}
You should get the options array values and join all the arrays
let models:[Model] = //...
let collectionViewArray = models.map { $0.value }.reduce([Option](), +)
Using for loop
var collectionViewArray = [Option]()
for model in models {
collectionViewArray.append(contentsOf: model.value)
}

Resources