This is a model of my makeshift database
var categories: [Category] = [
Category(name: "X",
sign: [Sign(code: "X-1", name: "***", description: "", picture: ""),
Sign(code: "X-2", name: "***", description: "", picture: "")]),
Category(name: "Y",
sign: [Sign(code: "Y-1", name: "Yyy", description: "", picture: ""),
Sign(code: "Y-2", name: "yyy", description: "", picture: "")]),
Category(name: "Z",
sign: [Sign(code: "Z-1", name: "Zzz", description: "", picture: ""),
Sign(code: "Z-2", name: "ZZZ", description: "", picture: "")])
]
I need to get one random element of any category (not one from each) from this base and print it as sign.code
I prepared the variable to store the value:
var RandomSign: Sign
And tried to do it with for loop:
func randomSign() {
for categorie in categories {
randomSign = categorie.sign.randomElement()
But, in the result, my loop generate me random element from each category, and finally save only random from the last one. I would like print for example "X-2" in my consol as random selected value.
Why not pick a random category and then a random sign from that random category?
func randomSign() {
randomSign = categories.randomElement()!.sign.randomElement()!
}
This code will crash if any of the arrays are empty. It would be safer if randomSign was optional then you can do:
func randomSign() {
randomSign = categories.randomElement()?.sign.randomElement()
}
Or you could fallback to a default Sign:
func randomSign() {
randomSign = categories.randomElement()?.sign.randomElement() ?? Sign(code: "empty", name: "***", description: "", picture: "")
}
Related
I have two models, ExerciseModel and RoutineModel, in which a single routine is comprised of one or more exercises. I am trying to use the contents of the data array from the ExerciseArrayObject class as part of my RoutineArrayObject's data array.
I am receiving the following error:
'self' used in property access 'exercises' before all stored properties are initialized
Here is ExerciseArrayObject:
import Foundation
import SwiftUI
class ExerciseArrayObject: ObservableObject {
#Published var dataArray = [ExerciseModel]()
init() {
//print("FETCH FROM DATABASE HERE")
let exercise1 = ExerciseModel(exerciseID: "", userID: "", username: "userA", exerciseTitle: "Exercise 1", dateCreate: Date(), exerciseImage: "logo", repsInfo: "12 reps", setsInfo: "3 sets")
let exercise2 = ExerciseModel(exerciseID: "", userID: "", username: "userB", exerciseTitle: "Exercise 2", dateCreate: Date(), exerciseImage: "logo", repsInfo: "8 reps", setsInfo: "4 sets")
let exercise3 = ExerciseModel(exerciseID: "", userID: "", username: "userC", exerciseTitle: "Exercise 3", dateCreate: Date(), exerciseImage: "logo", repsInfo: "5 reps", setsInfo: "5 sets")
let exercise4 = ExerciseModel(exerciseID: "", userID: "", username: "userD", exerciseTitle: "Exercise 4", dateCreate: Date(), exerciseImage: "logo", repsInfo: "20 reps", setsInfo: "10 sets")
let exercise5 = ExerciseModel(exerciseID: "", userID: "", username: "userE", exerciseTitle: "Exercise 5", dateCreate: Date(), exerciseImage: "logo", repsInfo: "10 reps", setsInfo: "3 sets", sharedUserUsername: "Shared User")
self.dataArray.append(exercise1)
self.dataArray.append(exercise2)
self.dataArray.append(exercise3)
self.dataArray.append(exercise4)
self.dataArray.append(exercise5)
}
}
And here is RoutineArrayObject:
class RoutineArrayObject: ObservableObject {
#ObservedObject var exercises: ExerciseArrayObject
#Published var dataArray = [RoutineModel]()
init() {
//print("FETCH FROM DATABASE HERE")
let routine1 = RoutineModel(routineID: "", userID: "", username: "user1", routineTitle: "Yoga Routine", exercises: exercises.dataArray, dateCreate: Date(), routineImage: "demoexercise", noOfExercises: "\(exercises.dataArray.count)")
let routine2 = RoutineModel(routineID: "", userID: "", username: "user2", routineTitle: "Core Routine", exercises: exercises.dataArray, dateCreate: Date(), routineImage: "logo", noOfExercises: "\(exercises.dataArray.count)", sharedUserID: "", sharedUserUsername: "Shared User")
self.dataArray.append(routine1)
self.dataArray.append(routine2)
}
}
Assuming ExerciseModel and RoutineModel are structs,
you could try this approach using completion closures. This
keeps your two models and use the contents of the data array from
the ExerciseArrayObject class as part of your RoutineArrayObject's data array.
class ExerciseArrayObject: ObservableObject {
#Published var dataArray = [ExerciseModel]()
init() {
ExerciseArrayObject.fetchExcersices() { arr in
self.dataArray = arr
}
}
static func fetchExcersices(completion: #escaping([ExerciseModel]) -> ()) {
//print("FETCH FROM DATABASE HERE")
var excersices = [ExerciseModel]()
let exercise1 = ExerciseModel(exerciseID: "", userID: "", username: "userA", exerciseTitle: "Exercise 1", dateCreate: Date(), exerciseImage: "logo", repsInfo: "12 reps", setsInfo: "3 sets")
let exercise2 = ExerciseModel(exerciseID: "", userID: "", username: "userB", exerciseTitle: "Exercise 2", dateCreate: Date(), exerciseImage: "logo", repsInfo: "8 reps", setsInfo: "4 sets")
let exercise3 = ExerciseModel(exerciseID: "", userID: "", username: "userC", exerciseTitle: "Exercise 3", dateCreate: Date(), exerciseImage: "logo", repsInfo: "5 reps", setsInfo: "5 sets")
let exercise4 = ExerciseModel(exerciseID: "", userID: "", username: "userD", exerciseTitle: "Exercise 4", dateCreate: Date(), exerciseImage: "logo", repsInfo: "20 reps", setsInfo: "10 sets")
let exercise5 = ExerciseModel(exerciseID: "", userID: "", username: "userE", exerciseTitle: "Exercise 5", dateCreate: Date(), exerciseImage: "logo", repsInfo: "10 reps", setsInfo: "3 sets", sharedUserUsername: "Shared User")
excersices.append(exercise1)
excersices.append(exercise2)
excersices.append(exercise3)
excersices.append(exercise4)
excersices.append(exercise5)
completion(excersices) // <-- todo deal with errors etc...
}
}
class RoutineArrayObject: ObservableObject {
#Published var dataArray = [RoutineModel]()
init() {
ExerciseArrayObject.fetchExcersices() { exercises in
print("--> exerciseArray: \(exercises)")
self.fetchRoutines(exercises: exercises) { _ in
print("--> routineArray: \(self.dataArray)")
}
}
}
func fetchRoutines(exercises: [ExerciseModel], completion: #escaping(Bool) -> ()) {
//print("FETCH FROM DATABASE HERE")
let routine1 = RoutineModel(routineID: "", userID: "", username: "user1", routineTitle: "Yoga Routine", exercises: exercises, dateCreate: Date(), routineImage: "demoexercise", noOfExercises: exercises.count)
let routine2 = RoutineModel(routineID: "", userID: "", username: "user2", routineTitle: "Core Routine", exercises: exercises, dateCreate: Date(), routineImage: "logo", noOfExercises: exercises.count, sharedUserID: "", sharedUserUsername: "Shared User")
self.dataArray.append(routine1)
self.dataArray.append(routine2)
completion(true) // <-- todo deal with errors etc...
}
}
You can also use other techniques instead of basic completion closures, such as
async/await concurrency with tasks.
I have 2 arrays of maps where one of them has product ids and quantities; and the other one has product ids, product names and price:
List<Map<String, dynamic>> arr1 = [
{ id: "1", name:"First Item", price: 10 },
{ id: "2", name: "Second Item", price: 12 }
];
List<Map<String, dynamic>> arr2 = [
{ id: "1", quantity: 1 },
{ id: "2", quantity: 3 },
{ id: "3", quantity: 2 }
];
Now I need to get total price of products by combining two arrays by obtaining sum of price * quantites.
I need an array similar to this:
List<Map<String, dynamic>> arr3 =[
{ id: "1", name:"First Item", price: 10, quantity: 1 },
{ id: "2", name: "Second Item", price: 12, quantity: 3 }
];
How can I merge them into one array based on their ids?
You can merge the array by mapping the first array to the second one.
final arr3 = arr1.map((product) {
final quantity = arr2
.where((quantities) => quantities["id"] == product["id"])
.map((quantities) => quantities["quantity"] as int)
.first;
return product..["quantity"] = quantity;
});
Full example: https://dartpad.dev/67148d132cb930bc6f1cee6a8a4fcff1
New to swiftUI, and I need to show the images and their names randomly by action of a button. How should I call the pictures' array to show a random picture each time user tap on the button in the contentView file ?
this is the array of pictures that I want to show them randomly in contentView(pictures placed in Assets folder):
struct aPicture: Identifiable {
var id: Int
var name: String
var imageName: String
}
let pictures = [
aPicture(id: 0, name: "1", imageName: "1"),
aPicture(id: 1, name: "2", imageName: "2"),
aPicture(id: 2, name: "3", imageName: "3"),
aPicture(id: 3, name: "4", imageName: "4"),
aPicture(id: 4, name: "5", imageName: "5"),
aPicture(id: 5, name: "6", imageName: "6"),
]
you can try this:
struct ContentView: View {
struct aPicture: Identifiable {
var id: Int
var name: String
var imageName: String
}
#State var random : Int = 0
let pictures = [
aPicture(id: 0, name: "1", imageName: "1"),
aPicture(id: 1, name: "2", imageName: "2"),
aPicture(id: 2, name: "3", imageName: "3"),
aPicture(id: 3, name: "4", imageName: "4"),
aPicture(id: 4, name: "5", imageName: "5"),
aPicture(id: 5, name: "6", imageName: "6"),
]
var body: some View {
VStack {
HStack {
Spacer()
Text(pictures[self.random].name)
.background(Color.white)
Spacer()
Button("Next image") {
self.random = Int.random(in: 0..<self.pictures.count)
}
Spacer()
}
Image(pictures[self.random].imageName)
.resizable()
.scaledToFit()
}
}
}
I suggest you different approach.
1) you can generate random element directly
let picture = pictures.randomElement() ?? default_if_empty_collection
2) user would like to see different picture after tap, which could not be true. Less pictures in your "store", more likely randomly you generate the same picture (which could looks like "nothing happens on tap")
next snippet shows how to solve this. On every tap the user see different picture, even though there are just tree pictures in our collection.
import SwiftUI
struct ContentView: View {
#State var img = Image(systemName: "questionmark.square.fill")
let imgs = [Image(systemName: "trash"),
Image(systemName: "trash.fill"),
Image(systemName: "trash.slash"),
]
var body: some View {
img
.resizable()
.scaledToFit()
.frame(maxWidth: .infinity)
.onTapGesture {
var tmp: Image
repeat {
tmp = self.imgs.randomElement() ?? self.img
} while tmp == self.img
self.img = tmp
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I have a sorted array
let things = [
Thing(activity: "1", name: "value1"),
Thing(activity: "1", name: "value2"),
Thing(activity: "1", name: "value3"),
Thing(activity: "2", name: "value4"),
Thing(activity: "2", name: "value5"),
Thing(activity: "3", name: "value6"),
Thing(activity: "3", name: "value7"),
Thing(activity: "1", name: "value8"),
Thing(activity: "1", name: "value9"),
Thing(activity: "1", name: "value10")
]
I would like to produce array of arrays splitted when the activity value changes similar to the following
[[Thing(activity: "1", name: "value1"),
Thing(activity: "1", name: "value2"),
Thing(activity: "1", name: "value3")],
[Thing(activity: "2", name: "value4"),
Thing(activity: "2", name: "value5")],
[Thing(activity: "3", name: "value6"),
Thing(activity: "3", name: "value7")],
[Thing(activity: "1", name: "value8"),
Thing(activity: "1", name: "value9"),
Thing(activity: "1", name: "value10")]]
A generalized solution would be:
extension Sequence {
func grouped<T: Equatable>(by block: (Element) throws -> T) rethrows -> [[Element]] {
return try reduce(into: []) { result, element in
if let lastElement = result.last?.last, try block(lastElement) == block(element) {
result[result.index(before: result.endIndex)].append(element)
} else {
result.append([element])
}
}
}
}
Then you can do:
let results = things.grouped { $0.activity }
A less elegant (but slightly more efficient) solution would be:
extension Sequence {
func grouped<T: Equatable>(by block: (Element) throws -> T) rethrows -> [[Element]] {
var results: [[Element]] = []
var lastValue: T?
var index = results.endIndex
for element in self {
let value = try block(element)
if let lastValue = lastValue, lastValue == value {
results[index].append(element)
} else {
results.append([element])
index = results.index(before: results.endIndex)
lastValue = value
}
}
return results
}
}
As already mentioned by #matt in comments you can use collection method reduce(into:) to group your elements by checking if the activity of the last element of the last array is equal to the current element activity, if so just append a new element to the last array, otherwise append a new array with a single element to the outer array:
struct Thing {
let activity, name: String
}
let things: [Thing] = [
.init(activity: "1", name: "value1"),
.init(activity: "1", name: "value2"),
.init(activity: "1", name: "value3"),
.init(activity: "2", name: "value4"),
.init(activity: "2", name: "value5"),
.init(activity: "3", name: "value6"),
.init(activity: "3", name: "value7"),
.init(activity: "1", name: "value8"),
.init(activity: "1", name: "value9"),
.init(activity: "1", name: "value10")]
let grouped: [[Thing]] = things.reduce(into: []) {
$0.last?.last?.activity == $1.activity ?
$0[$0.index(before: $0.endIndex)].append($1) :
$0.append([$1])
}
print(grouped) // "[[__lldb_expr_1.Thing(activity: "1", name: "value1"), __lldb_expr_1.Thing(activity: "1", name: "value2"), __lldb_expr_1.Thing(activity: "1", name: "value3")], [__lldb_expr_1.Thing(activity: "2", name: "value4"), __lldb_expr_1.Thing(activity: "2", name: "value5")], [__lldb_expr_1.Thing(activity: "3", name: "value6"), __lldb_expr_1.Thing(activity: "3", name: "value7")], [__lldb_expr_1.Thing(activity: "1", name: "value8"), __lldb_expr_1.Thing(activity: "1", name: "value9"), __lldb_expr_1.Thing(activity: "1", name: "value10")]]\n"
I have a set of object array like this:
[ProductDetails(name: "Tai Tou Choy",
productDescription: "",
deliveryTime: "24 hours",
img1: "https://api.com.my/images/data/4983-img1-1463122485.jpg",
thumbImg1: "https://api.com.my/images/data/thumbs/4983-img1-1463122485.jpg",
img2: "",
thumbImg2: "",
img3: "",
thumbImg3: "",
videoLink: "",
sku: "0000000004983",
relatedProduct: "",
priceOption: [PriceOption(id: 9931,
label: "500g",
price: "4.56",
promo: "0",
quantity: 999)],
deliveryZone: [DeliveryZone(zone: "3")],
deliveryZoneName: [DeliveryZoneName(zoneName: "Within Klang Valley")],
qrCode: "4983"),
ProductDetails(name: "Penguin Asam Jawa",
productDescription: "",
deliveryTime: "24 hours",
img1: "https://api.com.my/images/data/1004-img1.jpg",
thumbImg1: "https://api.com.my/images/data/thumbs/1004-img1.jpg",
img2: "",
thumbImg2: "",
img3: "",
thumbImg3: "",
videoLink: "",
sku: "0000000001004",
relatedProduct: "",
priceOption: [PriceOption(id: 6971,
label: "1 kg",
price: "4.80",
promo: "0",
quantity: 864)],
deliveryZone: [DeliveryZone(zone: "3")],
deliveryZoneName: [DeliveryZoneName(zoneName: "Within Klang Valley")],
qrCode: "1004")]
I wanted to do price sorting from above the arrays, which the PriceOption array is store inside the main array as an object array too. How do i do sorting from PriceOption? I try to do it in this way :
for i in self.productList{
i.priceOption.sortInPlace{
$0.price.localizedCaseInsensitiveCompare($1.price) == NSComparisonResult.OrderedDescending
}
}
Which return an error "Cannot use mutating member on immutable value :'i' is a 'let' constant."
How to do this?
This how I solve issue that you present. I am using sorted method of the array class.
Solution:
self.productList.sorted{ Float($0.priceOption.first!.price)! > Float($1.priceOption.first!.price)! }
Try this
for i in self.productList {
i.priceOption.sort { $0.price < $1.price } }
The answer for swift 3 is as following
Ascending:
self.productList = self.productList.sorted(by:
{(first: ProductDetails, second: ProductDetails) -> Bool in
first.price > second.price
}
)
Descending:
self.productList = self.productList.sorted(by:
{(first: ProductDetails, second: ProductDetails) -> Bool in
first.price > second.price
}
)