Pick conditional values from ForEach in SwiftUI - swiftui

I have this struct created.
struct MedItem: Identifiable {
let id = UUID() //Generates a unique string for each item listed below
let medThumbnail: String
let medTitle: String
let medDose: String
let medDoseval: String
let purpose: String
let takeAt: String
let refillDate: String
let refillRem: String
}
With this data added:
struct MedData {
static let mListData = [
MedItem(medThumbnail: "Levo", medTitle: "Levothyroxine", medDose: "15", takeAt: "8 am", medDoseval: "mcg", purpose: "Hypothyroidism", refillDate: "December 3, 2021", refillRem: "Refill Remaning: 1"),
] //Imagine more sample data here
I'm trying to sort values based on time range linked to the timeAt value.
ForEach (meds, id: \.id) { MedItem in
HStack(spacing: 10.0){
}
If time is between 2am - 12pm then morning, 12pm - 3pm then noon, 3pm - 6pm evening, and 7pm - 2am then night. Some meds will not have a time and they need to be catoegorized in a different bucket.
Thank you for your help :)

Related

Sum time based chart data

I have a chart which displays data over time (months in my case). The data for this chart is randomly generated by getting any date from now to 500 days in the past (chartDate), and a random number (sales). It generates 500 of these rows.
func dailySales2() -> [(chartDate: Date, year: String, sales: Int)] {
var temp: [(chartDate: Date, year: String, sales: Int)] = []
for _ in 1...500 {
let dateT = Calendar.current.date(byAdding: .day, value: -Int.random(in: 1...500), to: Date())!
temp.append((chartDate: dateT, year: dateT.formatted(.dateTime.year()), sales: Int.random(in: 100...500)))
}
return temp.sorted(by: { $0.chartDate < $1.chartDate })
}
Chart {
ForEach(dailySales2(), id: \.chartDate) { chartD in
BarMark(
x: .value("Day", chartD.chartDate, unit: .month),
y: .value("Sales", chartD.sales)
)
.foregroundStyle(by: .value("Year:", chartD.year))
}
}
The above takes all the rows for each month and totals them, so each bar is a sum of all the sales for each month. If I change this to a LineMark, it displays each row individually, how can I stop this and instead sum all the individual rows into one point for each month? Thanks.
I'm expecting each month column to display a single point which sums all the rows that contains data for that month.

SwiftUI View ForEach loop from last to first

How can I loop from last item to first in a SwiftUi view?
I've tried the following
let myarray = ["1", "2", "3", "4"]
ForEach(stride(from: myarray.count, to: 1, by: -1), id: \.self) { i in
print(myarray[i])
}
//desired output: 4 3 2 1
Thanks!!
You can use reversed:
ForEach(myarray.reversed(), id: \.self) { item in
Text("\(item)")
}
Note that I'm using Text, not print -- this is SwiftUI, so you should have View code inside a ForEach.
Also note that \.self is generally dangerous in ForEach, but I'm assuming this is just sample code. Generally, you want something truly uniquely Identifiable

How can I create a dynamic input to .chartForegroundStyleScale

In Swift Charts the signature for chartForegroundStyleScale to set the ShapeStyle for each data series is:
func chartForegroundStyleScale<DataValue, S>(_ mapping: KeyValuePairs<DataValue, S>) -> some View where DataValue : Plottable, S : ShapeStyle
The KeyValuePairs initialiser (init(dictionaryLiteral: (Key, Value)...)) only takes a variadic parameter so any attempt to initialise a foreground style from an array (in my case <String, Color>) results in the error:
Cannot pass array of type '[(String, Color)]' as variadic arguments of type '(String, Color)'
In my application the names of the chart series are set dynamically from the data so although I can generate a [String : Color] dictionary or an array of (String, Color) tuples I can't see that it's possible to pass either of these into chartForegroundStyleScale? Unless I'm missing something this seems like a odd limitation in Swift charts that the series names need to be hard coded for this modifier?
OK I've found an approach that works as long as an arbitrary limitation to the number of entries is acceptable (example below with max size of 4:
func keyValuePairs<S, T>(_ from: [(S, T)]) -> KeyValuePairs<S, T> {
switch from.count {
case 1: return [ from[0].0 : from[0].1 ]
case 2: return [ from[0].0 : from[0].1, from[1].0 : from[1].1 ]
case 3: return [ from[0].0 : from[0].1, from[1].0 : from[1].1, from[2].0 : from[2].1 ]
default: return [ from[0].0 : from[0].1, from[1].0 : from[1].1, from[2].0 : from[2].1, from[3].0 : from[3].1 ]
}
In my case I know that there won't be more than 20 mappings so this func can just be extended to accommodate that number.
Not ideal, but it works...
You could also pass an array of colors to .chartForegroundStyleScale(range:). As long as you add the colors to the array in the same order you add your graph marks it should work fine.
Not incredibly elegant either, but this approach works with an arbitrary number or entries.
struct GraphItem: Identifiable {
var id = UUID()
var label: String
var value: Double
var color: Color
}
struct ContentView: View {
let data = [
GraphItem(label: "Apples", value: 2, color: .red),
GraphItem(label: "Pears", value: 3, color: .yellow),
GraphItem(label: "Melons", value: 5, color: .green)
]
var body: some View {
Chart {
ForEach(data, id: \.label) { item in
BarMark(
x: .value("Count", item.value),
y: .value("Fruit", item.label)
)
.foregroundStyle(by: .value("Fruit", item.label))
}
}
.chartForegroundStyleScale(range: graphColors(for: data))
}
func graphColors(for input: [GraphItem]) -> [Color] {
var returnColors = [Color]()
for item in input {
returnColors.append(item.color)
}
return returnColors
}
}

bring an https request into an object

I'm new to flutter and even coding itself. I have a bit experience in Java and C# so I know datatypes and functions, etc.
My problem: I'm not realy firm in lists and maps right now but I have to send a https request and I will receive a list that contains other lists:
example:
[
[
"60",
"49.142000",
"9.362000",
8,
"Obersulmer Kachelofenbau",
"Am Seebach 6",
"74182",
"Obersulm-Willsbach",
"www.obersulmer-kachelofenbau.de",
"",
"07134 - 5 10 36 ",
"info#obersulmer-kachelofenbau.de",
"1557919527",
"DE"
],
[
"48",
"48.917000",
"9.425000",
26,
"K\u00f6gel Schornsteine GmbH",
"Donaustra\u00dfe 17 - 19",
"71522",
"Backnang",
"www.koegel-schornsteine.de",
"",
"07191 95255-40",
"info#koegel-schornsteine.de",
"1557989245",
"DE"
],
]
I created a class to store these data:
class Store {
final String id;
final double lat;
final double long;
final String name;
final String street;
final String zip;
final String city;
final String web;
final String phone;
final String mail;
final String countryCode;
Store(this.id, this.lat, this.long, this.name, this.street, this.zip,
this.city, this.web, this.phone, this.mail, this.countryCode);
}
I don´t need all data of the incoming lists. Only index 0,1,2,4,5,6,7,8,10,11,13 are needed
When I look at the cookbook (https://flutter.dev/docs/cookbook/networking/fetch-data)
it tells me to create the album class (in my case the store class) but it works with a json and I don´t get a json. Maybe I missunderstand the cookbook but in general lists and maps in flutter is not my passion.
Hopefully I provided all infomrations you need in a clear way. If not please ask me.
My main issue is how to get the data I receive into the store class?? I appriciate any help from you.
It's better to use json format for your network api.
If you could not use json, you can use below code. Please make sure your api result format is exactly like above, i mean your requested index should be ready.
void main() async {
List<List> apiResults = [
[
"60",
"49.142000",
"9.362000",
8,
"Obersulmer Kachelofenbau",
"Am Seebach 6",
"74182",
"Obersulm-Willsbach",
"www.obersulmer-kachelofenbau.de",
"",
"07134 - 5 10 36 ",
"info#obersulmer-kachelofenbau.de",
"1557919527",
"DE"
],
[
"48",
"48.917000",
"9.425000",
26,
"K\u00f6gel Schornsteine GmbH",
"Donaustra\u00dfe 17 - 19",
"71522",
"Backnang",
"www.koegel-schornsteine.de",
"",
"07191 95255-40",
"info#koegel-schornsteine.de",
"1557989245",
"DE"
],
];
List<Store> stores = [];
for (var item in apiResults) {
stores.add(Store(
id: item[0],
lat: double.parse(item[1]),
long: double.parse(item[2]),
name: item[4],
street: item[5],
zip: item[6],
city: item[7],
web: item[8],
phone: item[10],
mail: item[11],
countryCode: item[13],
));
}
print(stores);
}
class Store {
final String? id;
final double? lat;
final double? long;
final String? name;
final String? street;
final String? zip;
final String? city;
final String? web;
final String? phone;
final String? mail;
final String? countryCode;
Store(
{this.id,
this.lat,
this.long,
this.name,
this.street,
this.zip,
this.city,
this.web,
this.phone,
this.mail,
this.countryCode});
}
The response from your API can still be parsed as JSON with jsonDecode from the package dart:convert. But the example response you provided contains a trailing comma after the last array, which isn't valid JSON. Therefore, a regular expression can be used to replace the ,] with only ]. This effectively removes the leading commas everywhere, making it valid JSON.
jsonDecode will return your data in a list with lists inside, which can be used to create your objects.
import 'dart:convert';
parse() {
String response = '''[
[
"60",
"49.142000",
"9.362000",
8,
"Obersulmer Kachelofenbau",
"Am Seebach 6",
"74182",
"Obersulm-Willsbach",
"www.obersulmer-kachelofenbau.de",
"",
"07134 - 5 10 36 ",
"info#obersulmer-kachelofenbau.de",
"1557919527",
"DE",
],
[
"48",
"48.917000",
"9.425000",
26,
"K\u00f6gel Schornsteine GmbH",
"Donaustra\u00dfe 17 - 19",
"71522",
"Backnang",
"www.koegel-schornsteine.de",
"",
"07191 95255-40",
"info#koegel-schornsteine.de",
"1557989245",
"DE",
],
]''';
response = response.replaceAll(new RegExp(r',( +|\n)( +)]'), ']');
return jsonDecode(response);
}
void main(List<String> arguments) {
var json = parse();
print(json[0]);
print(json[1]);
}

How to insert into Sqlite with optional parameters using Swift 3

I have this class with some optional properties:
class Address {
var Id: Int64
var AddressType: Int64
var AddressStatus: Int64
var Address1: String?
var Address2: String?
var City: String?
var State: String?
var Zip: String?
var Country: String?
var Latitude: Double?
var Longitude: Double?
}
I am trying to insert into a Sqlite database, like this:
let insert = table.insert(or: .replace, Id <- item.Id, AddressType <- item.AddressType, AddressStatus <- item.AddressStatus, Address1 <- item.Address1?, Address2 <- item.Address2?, City <- item.City?, State <- item.State?, Zip <- item.Zip?, Country <- item.Country?, Latitude <- item.Latitude?, Longitude <- item.Longitude?)
But I get this build error:
Value of optional type 'String?' not unwrapped; did you mean to use '!' or '?'?
If I use '!' it will build, but I get this error when I run:
unexpectedly found nil while unwrapping an Optional value
I am still new to Swift, but from what I know, I don't want to use '!' when the value can be nil, correct?
I'm not sure what I'm doing wrong here.
Make all your class properties constants (declare with let) and make them non optional adding a required init() with all your Address properties parameters. BTW it is Swift convention to start your vars naming with a lowercase letter. Note: You should use Int instead of Int64 unless it is a requirement. Regarding the optionality of your properties you can just assign a default value for them at initialization time. Btw It is better to use a struct unless you need to make your object persist (NSCoding compliant):
struct Address {
let id: Int
let addressType: Int
let addressStatus: Int
let address1: String
let address2: String
let city: String
let state: String
let zip: String
let country: String
let latitude: Double?
let longitude: Double?
init(id: Int, addressType: Int, addressStatus: Int, address1: String = "", address2: String = "", city: String = "", state: String = "", zip: String = "", country: String = "", latitude: Double = nil, longitude: Double = nil) {
self.id = id
self.addressType = addressStatus
self.addressStatus = addressStatus
self.address1 = address1
self.address2 = address2
self.city = city
self.state = state
self.zip = zip
self.country = country
self.latitude = latitude
self.longitude = longitude
}
}
So you can create your new object Address as follow:
let address1 = Address(id: 1, addressType: 2, addressStatus: 3)
let address2 = Address(id: 2, addressType: 3, addressStatus: 4, address1: "Any Address", latitude: 22.0, longitude: 43.0) // You don't need to add all parameters as long as you keep them in the same order as your initializer
address2.latitude // 22.0