How to iterate through array of Images in SwiftUI eloquently? - image

I'd like to iterate through array of Images https://developer.apple.com/documentation/swiftui/image
in manner like List(images) or ForEach(images) but not through indexes of the array
List(images, id : \.hashValue){ $0 }
for using List or ForEach, Image must be hashable or identifiable
extension Image: Hashable{
public func hash(into hasher: inout Hasher) {
hasher.combine( / some value / )
}
}
what'd better to use as a parameter for "combine" ? or any suggestions how to implement looping eloquently ?

How about wrapping each image in an Identifiable struct, and then using the id as the hash value…
struct ImageModel: Identifiable, Hashable {
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
let id = UUID()
let image: Image
}
struct ContentView: View {
let images = [ImageModel(image:Image(systemName: "questionmark")),
ImageModel(image:Image(systemName: "square")),
ImageModel(image:Image(systemName: "circle"))]
var body: some View {
List {
ForEach(images) { model in
model.image
}
}
}
}

SwiftUI is a declarative language and asking how to iterate something is imperative so it's not possible.

Related

How to define generic function with custom structs without listing all of them?

Let's say I have two different structs:
type One struct {
Id string
// Other fields
}
type Two struct {
Id string
// Other fields
}
Is it possible to define a function that accepts both One and Two without explicitly listing them as options?
E.g. I am looking for something like this:
type ModelWithId struct {
Id string
}
func Test[M ModelWithId](m M) {
fmt.PrintLn(m.Id)
}
one := One { Id: "1" }
Test(one) // Prints 1
I don't want to use funcTest[M One | Two](m M), because I'll likely have 10+ structs and I don't want to come back to the function every time I add a new struct to the codebase.
Generics constraints the type parameter behaviours using methods, so you need to rewrite your code as:
type One struct {
id string
}
func (o *One) Id() string {
return o.id
}
then your use site would become:
type ModelWithId interface {
Id() string
}
func Test[M ModelWithId](m M) {
fmt.Println(m.Id())
}

Extracting single String/Int from fetched API data list

I am struggling to extract single values (strings and Int) from the data I fetched from an API. I fetch the data in the form of a list:
class apiCall {
func getRockets(completion:#escaping ([Rockets]) -> ()) {
guard let url = URL(string: "https://api.spacexdata.com/v4/rockets") else { return }
URLSession.shared.dataTask(with: url) { (data, _, _) in
let rockets = try! JSONDecoder().decode([Rockets].self, from: data!)
print(rockets)
DispatchQueue.main.async {
completion(rockets)
}
}
.resume()
}
}
Then when I try using it in a View I succeed when using a List to view the values, like it shows the list of the names from the API data. But then when I want to view a single value like this:
import SwiftUI
struct RocketStatistics: View {
#State var rockets: [Rockets] = []
var body: some View {
VStack{
Text(rockets[1].name)
}
.onAppear {
apiCall().getRockets { (rockets) in
self.rockets = rockets
}
}
}
}
struct RocketStatistics_Previews: PreviewProvider {
static var previews: some View {
RocketStatistics()
}
}
It does not even give me an error, but my preview just won't update and keeps crashing.
So my question is how can I extract single values from this API data in List form and use these single values in my whole project?
To keep it simple and make it work first I started just with fetching the "name" from the API:
import Foundation
import SwiftUI
struct Rockets: Codable, Identifiable {
let id = UUID()
let name : String
}
When it all works I would also want to use Integer values from the API in my project, so tips on how to that are also welcome!
Never ever get items by a hard-coded index in a SwiftUI view without any check. When the view is rendered the first time the array is empty and any index subscription will crash.
Always check if the array contains the required number of items. In this case the array must contain at least two items
VStack{
Text(rockets.count > 1 ? rockets[1].name : "")
}

Xcode View - trying to display the name (a var) of a struc as text

I have this model where I have a list of boat, and a list of works (which are linked to a boat). All are linked to the user. My data is stored in Firestore by my repository.
struct boat: Codable, Identifiable {
#DocumentID var id : String?
var name: String
var description: String
#ServerTimestamp var createdtime: Timestamp?
var userId: String?
}
struct Work: Identifiable, Codable {
#DocumentID var id : String?
var title: String
var descriptionpb: String
var urgent: Bool
#ServerTimestamp var createdtime: Timestamp?
var userId: String?
var boatId: String // (boatId = id of struct boat just above)
}
I have a view in which I want to display (and let the user edit) the details of the work (such as the title and descriptionpb), I manage to display the boatId (see below), however I want to display the boat name. How should I go about it?
import SwiftUI
struct WorkDetails: View {
#ObservedObject var wcvm: WorkCellVM
#ObservedObject var wlvm = WorklistVM()
#State var presentaddwork = false
var onCommit: (work) -> (Void) = { _ in }
var body: some View {
ScrollView {
VStack(alignment: .leading) {
Text(wcvm.work.boatId) // <-- THIS IS WHAT I WANT TO CHANGE INTO boat name instead of boatId
.padding()
TextField("Enter work title", text: $wcvm.work.title, onCommit: {
self.onCommit(self.wcvm.work)
})
.font(.title)
.padding()
HStack {
TextField("Enter problem description", text: $wcvm.work.descriptionpb, onCommit: {
self.onCommit(self.wcvm.work)
})
}
.font(.subheadline)
.foregroundColor(.secondary)
.padding()
}
}
}
}
Essentially you have a Data Model problem, not a SwiftUI problem. I would be keeping all of this in Core Data and linking the various models(Entities in Core Data) with relationships. So your Work(Essentially a work order) would link to the boat that the work was being performed on.
Otherwise, you need to add a Boat as a parameter to Work. Either way would give you the desired syntax, but Core Data is much more efficient. It is also your data persistence model so you would kill two birds with one stone.
Solution found: when creating a work order, I was assigning the boat id, I am now assigning the boat name as well (and calling it in the work order display). Essentially keeping the same code as above, tweaking it a little bit so that it does what I want to do.

Swift 5.0 Generate subset of object array

I have a struct model called Questions stored in DataRep which is also stored in EnvironmentObject. There are 100+ questions in 12 categories.
struct Question: Hashable, Codable, Identifiable {
var id: Int
var questionText: String
var questionComment: String
var category: String
}
class DataRep: ObservableObject {
#Published var QuestionList : [Question] = QuestionListData
#Published var selectedCategory = "all"
}
On the user interface, I placed 12 buttons at top and list view down to list the questions in that category.
When user clicks on a new category, I update the selectedCategory parameter and filter the main questions list object to select the relevant questions.
struct QuestionList: View {
#EnvironmentObject var datarep: DataRep
var body: some View {
NavigationView{
Form {
ForEach(self.filterQuestions(datarep.QuestionList)) { question in
HStack{
QuestionView (question: question)
}
}
}//List
.navigationBarTitle(self.datarep.selectedCategory )
.labelsHidden()
.listStyle(GroupedListStyle())
}
}
func filterQuestions(_ activeList : [Question]) -> [Question]
{
if self.datarep.selectedCategory != "all" {
return activeList.filter{ $0.category.contains(self.datarep.selectedCategory) }
}
return activeList
}
}
However I am running into issues with filter method as it is generating a new array each time category is changed. There is no way I know to create a binding.
any suggestions?
Regards,
M
Assuming the QuestionView will take binding (to change the question), as
struct QuestionView: View {
#Binding var question: Question
...
it can be bound (even after filtering) via main container like following
ForEach(self.filterQuestions(datarep.QuestionList)) { question in
HStack{
QuestionView (question:
self.$datarep.QuestionList[self.datarep.QuestionList.firstIndex(of: question)!])
}
}

Enums with data in swift

I would like to use java-like enums, where you can have enum instances with custom data. For instance:
enum Country {
case Moldova(capital: "Chișinău", flagColors: [Color.Blue, Color.Yellow, Color.Red]);
case Botswana(capital: "Gaborone", flagColors: [Color.Blue, Color.White, Color.Black]);
}
I could later write:
Country.Moldova.capital;
It seems that I can indicate the variables, but not the values, and I can only assign the values when using the enum, not declaring. Which would be the best way to mimic this behaviour?
you can do something like this, which may be helpful: (that is a very generic example only)
enum Country : Int {
case Moldova, Botwana;
//
func capital() -> String {
switch (self) {
case .Moldova:
return "Chișinău"
case .Botwana:
return "Gaborone"
default:
return ""
}
}
//
func flagColours() -> Array<UIColor> {
switch (self) {
case .Moldova:
return [UIColor.blueColor(), UIColor.yellowColor(), UIColor.redColor()]
case .Botwana:
return [UIColor.blueColor(), UIColor.whiteColor(), UIColor.blackColor()]
default:
return []
}
}
//
func all() -> (capital: String, flagColours: Array<UIColor>) {
return (capital(), flagColours())
}
//
var capitolName: String {
get {
return capital()
}
}
//
var flagColoursArray: Array<UIColor> {
get {
return flagColours()
}
}
}
then you can access to the details like this:
let country: Country = Country.Botwana
get the capital
that way:
let capital: String = country.capital()
or another:
let capital: String = country.all().capital
or a third one:
let capital: String = country.capitolName
get the flag's colours:
that way:
let flagColours: Array<UIColor> = country.flagColours()
or another:
let flagColours: Array<UIColor> = country.all().flagColours
or a third one:
let flagColours: Array<UIColor> = country.flagColoursArray
Here is another generic example that is similar to the one posted by holex, but a bit closer to how it looks in Java. (I like it because all of the custom data is in one place). Instead of switching on 'self' in separate methods, I simply create a static dictionary that maps each case to a tuple containing the appropriate data. Then, I have convenience var's to get at the data easily.
enum TestEnum {
case One
case Two
case Three
private static let associatedValues = [
One: (int: 1, double: 1.0, string: "One"),
Two: (int: 2, double: 2.0, string: "Two"),
Three: (int: 3, double: 3.0, string: "Three")
]
var int: Int {
return TestEnum.associatedValues[self]!.int;
}
var double: Double {
return TestEnum.associatedValues[self]!.double;
}
var string: String {
return TestEnum.associatedValues[self]!.string;
}
}
You can access the custom data like so:
println(TestEnum.One.int) // 1
println(TestEnum.Two.double) // 2.0
println(TestEnum.Three.string) // Three
Unfortunately it seems like enums with raw values are limited to literal values. You may want to file a bug.
As an alternative, you could do something like this:
let Country = (
Moldova: (capital: "Chișinău", flagColors: [Color.Blue, Color.Yellow, Color.Red]),
Botswana: (capital: "Gaborone", flagColors: [Color.Blue, Color.White, Color.Black])
)
or this:
struct Country {
let capital: String
let flagColors: [Color]
}
let Countries = (
Moldova: Country(capital: "Chișinău", flagColors: [.Blue, .Yellow, .Red]),
Botswana: Country(capital: "Gaborone", flagColors: [.Blue, .White, .Black])
)

Resources