Swift - Problem with ForEach statement while structure is empty - swift

I have a forEach statement that illiterates through a struct containing meal entries. If the struct is empty, I would like to display alternative Text.
My issue is when the array is empty, I receive the following error when compiling
Cannot convert value of type 'String' to expected argument type 'Meal'
let mealEntrysDinner: [String] = [
]
Section(header:Text("Dinner")){
ForEach(mealEntrysDinner, id: \.self){ meal in
if(mealEntrysDinner.isEmpty == true){
Text("Arr Empty")
}
else{
EntryRow(meal:meal)
}
}
}
Meal Structure below:
struct Meal: Identifiable, Hashable{
var id = UUID()
var mealName: String
var calories: String
var quantity: Int
var amount: String
var protein: String
}
EntryRow
struct EntryRow: View {
var meal: Meal
var body: some View {
HStack{
VStack(alignment: .leading){
Text(meal.mealName)
Text(meal.amount)
.font(.subheadline)
.foregroundColor(.gray)
}
Spacer()
Text(meal.calories)
}
}
}

There are two issues.
First, it looks like you want mealEntrysDinner to be [Meal], not [String] -- otherwise, as you've discovered, the types don't match and you can't use EntryRow.
Secondly, you have something out-of-order with your if clause -- it should be outside the ForEach:
Section(header:Text("Dinner")){
if mealEntrysDinner.isEmpty {
Text("Arr Empty")
} else {
ForEach(mealEntrysDinner) { meal in
EntryRow(meal:meal)
}
}
}
This way, the ForEach only gets displayed if mealEntrysDinner has elements.

Related

SwiftUI Search bar crashes when trying to filter results

I have a textfield that, when active, shows the filtered results from an array of "City" structs. When I type certain letters it behaves as expected, but typing other letters (consistent) crashes the app with "Fatal error: each layout item may only occur once: file SwiftUI". The city struct has an id and has the Identifiable and Hashable protocols. Here is a snippet of the code that might help troubleshoot:
struct City: Codable, Identifiable, Hashable {
let id: String
let city: String
let state: String
}
#State var cities : [City] = Bundle.main.decode("cities.json")
...
TextField(placeholder, text: $searchText) { (isEditing) in
showingDropdown = isEditing
}
...
if showingDropdown && !searchText.isEmpty {
ScrollView {
LazyVStack {
ForEach(cities.filter { $0.city.contains(searchText)}) { city in
HStack {
Text(city.city)
Text("(\(city.id))")
.foregroundColor(.lightGrayWeFly)
}
.padding(.vertical, 5)
.onTapGesture(perform: {
input = city.id
searchText = city.city
showingDropdown.toggle()
})
}
}
}
}
It's possible that I made an error when I created the JSON file but I'm having trouble narrowing things down.

SwiftUI - View showing Elements conforming to a Protocol and ForEach over them

I want to write a SwiftUI View ListOfMyStruct that operates on an Array of Elements conforming to a given Protocol MyProtocol.
The example works, but of course I need to ForEach over the Elements of the Array (as tried in the commented out lines).
Using ForEach, I get: Value of protocol type 'MyProtokoll' cannot conform to 'Hashable'; only struct/enum/class types can conform to protocols.
If I try to let MyProtocol conform to Hashable I get: Protocol 'MyProtokoll' can only be used as a generic constraint because it has Self or associated type requirements.
How can I archive this?
struct PTest: View {
#State var allMyStruct : [MyStruct] =
[ MyStruct(name: "Frank Mueller", myshortName: "fm", a_lot_of_other_sttributes: "bla"),
MyStruct(name: "Tom Smith", myshortName: "ts", a_lot_of_other_sttributes: "bla")
]
var body: some View {
VStack {
Text("Hello, world!")
ListOfMyStruct(elements: allMyStruct)
}
.frame(width: 400, height: 400, alignment: .center)
.padding()
}
}
struct ListOfMyStruct: View {
var elements : [MyProtokoll]
var body: some View {
HStack {
Text("Elements of MyProtocol: ")
Text("\(elements[0].shortName() )")
Text("\(elements[1].shortName() )")
// ForEach(elements, id: \.self) { myProtocol in
// Text("\(myProtocol.shortName())")
// }
}
}
}
//protocol MyProtokoll : Identifiable, Hashable {
protocol MyProtokoll {
var id: String { get }
func shortName() -> String
}
struct MyStruct :MyProtokoll {
var id : String {myshortName}
var name : String
var myshortName :String
var a_lot_of_other_sttributes : String
func shortName() -> String {
myshortName
}
}
It is possible to use iteration by indices.
Here is a fixed part (tested with Xcode 12.4)
HStack {
// Text("Elements of MyProtocol: ")
// Text("\(elements[0].shortName() )")
// Text("\(elements[1].shortName() )")
ForEach(elements.indices, id: \.self) { i in
Text("\(elements[i].shortName())")
}
}
also the solution is to specify id explicitly, like
ForEach(elements, id: \.id) { myProtocol in
Text("\(myProtocol.shortName())")
}

Binding Array of Structs with Strings in an Array of Structs

I'm new to Swift so I hope this isn't something really silly. I'm trying to build an array of Structs, and one of the parameters is another Array with another Struct in it. I'm not sure if there is a better way, but I thought I was making really good progress right up till I tried to edit the embedded Struct. In it's simplified form it looks like this ...
struct Group: Identifiable, Codable {
var id = UUID()
var name: String
var number: Int
var spaces: Bool
var businesses: [Business]
}
struct Business: Identifiable, Codable {
var id = UUID()
var name: String
var address: String
var space: Int
var enabled: Bool
}
These are used in a class with an Observable var that stored in User Defaults
class GroupSettings: ObservableObject {
#Published var groups = [Group]() {
didSet {
UserDefaults.standard.set(try? PropertyListEncoder().encode(groups), forKey: "groups")
}
}
init() {
if let configData = UserDefaults.standard.value(forKey: "groups") as? Data {
if let userDefaultConfig = try?
PropertyListDecoder().decode(Array<Group>.self, from: configData){
groups = userDefaultConfig
}
}
}
}
Its passed in to my initial view and then I'm wanting to make an "Edit Detail" screen. When it gets to the edit detail screen, I can display the Business information in a Text display but I can't get it to working a TextField, it complains about can't convert a to a Binding, but the name from the initial Struct works fine, similar issues with the Int ...
I pass a Group from the first view which has the array of Groups in to the detail screen with the #Binding property ...
#Binding var group: Group
var body: some View {
TextField("", text: $group.name) <---- WORKS
List {
ForEach(self.group.businesses){ business in
if business.enabled {
Text(business.name) <---- WORKS
TextField("", business.address) <---- FAILS
TextField("", value: business.space, formatter: NumberFormatter()) <---- FAILS
} else {
Text("\(business.name) is disabled"
}
}
}
}
Hopefully I've explained my self well enough, and someone can point out the error of my ways. I did try embedding the 2nd Struct inside the first but that didn't help.
Thanks in advance!
You could use indices inside the ForEach and then still use $group and accessing the index of the businesses via the index like that...
List {
ForEach(group.businesses.indices) { index in
TextField("", text: $group.businesses[index].address)
}
}
An alternative solution may be to use zip (or enumerated) to have both businesses and its indices:
struct TestView: View {
#Binding var group: Group
var body: some View {
TextField("", text: $group.name)
List {
let items = Array(zip(group.businesses.indices, group.businesses))
ForEach(items, id: \.1.id) { index, business in
if business.enabled {
Text(business.name)
TextField("", text: $group.businesses[index].address)
} else {
Text("\(business.name) is disabled")
}
}
}
}
}

SwiftUI core data, grouped list fetch result

using core data im storing some airport and for every airport i'm storing different note
I have created the entity Airport and the entity Briefing
Airport have 1 attribute called icaoAPT and Briefing have 4 attribute category, descript, icaoAPT, noteID
On my detailsView I show the list all the noted related to that airport, I managed to have a dynamic fetch via another view called FilterList
import SwiftUI
import CoreData
struct FilterLIst: View {
var fetchRequest: FetchRequest<Briefing>
#Environment(\.managedObjectContext) var dbContext
init(filter: String) {
fetchRequest = FetchRequest<Briefing>(entity: Briefing.entity(), sortDescriptors: [], predicate: NSPredicate(format: "airportRel.icaoAPT == %#", filter))
}
func update(_ result : FetchedResults<Briefing>) ->[[Briefing]]{
return Dictionary(grouping: result) { (sequence : Briefing) in
sequence.category
}.values.map{$0}
}
var body: some View {
List{
ForEach(update(self.fetchRequest.wrappedValue), id: \.self) { (section : Briefing) in
Section(header: Text(section.category!)) {
ForEach(section, id: \.self) { note in
Text("hello")
/// Xcode error Cannot convert value of type 'Text' to closure result type '_'
}
}
}
}
}
}
on this view I'm try to display all the section divided by category using the func update...
but Xcode give me this error , I can't understand why..Cannot convert value of type 'Text' to closure result type '_'
fore reference I list below my detailsView
import SwiftUI
struct DeatailsView: View {
#Environment(\.managedObjectContext) var dbContext
#Environment(\.presentationMode) var presentation
#State var airport : Airport
#State var note = ""
#State var noteTitle = ["SAFTY NOTE", "TAXI NOTE", "CPNOTE"]
#State var notaTitleSelected : Int = 0
#State var notaID = ""
var body: some View {
Form{
Section(header: Text("ADD NOTE Section")) {
TextField("notaID", text: self.$notaID)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
TextField("add Note descrip", text: self.$note)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
Picker(selection: $notaTitleSelected, label: Text("Class of Note")) {
ForEach(0 ..< noteTitle.count) {
Text(self.noteTitle[$0])
}
}
HStack{
Spacer()
Button(action: {
let nota = Briefing(context: self.dbContext)
nota.airportRel = self.airport
nota.icaoAPT = self.airport.icaoAPT
nota.descript = self.note
nota.category = self.noteTitle[self.notaTitleSelected]
nota.noteID = self.notaID
do {
try self.dbContext.save()
debugPrint("salvato notazione")
} catch {
print("errore nel salva")
}
}) {
Text("Salva NOTA")
}
Spacer()
}
}
Section(header: Text("View Note")) {
FilterLIst(filter: airport.icaoAPT ?? "NA")
}
}
}
}
thanks for the help
This is because you try to iterate over a single Briefing object and a ForEach loop expects a collection:
List {
ForEach(update(self.fetchRequest.wrappedValue), id: \.self) { (section: Briefing) in
Section(header: Text(section.category!)) {
ForEach(section, id: \.self) { note in // <- section is a single object
Text("hello")
/// Xcode error Cannot convert value of type 'Text' to closure result type '_'
}
}
}
}
I'd recommend you to extract the second ForEach to another method for clarity. This way you can also be sure you're passing the argument of right type ([Briefing]):
func categoryView(section: [Briefing]) -> some View {
ForEach(section, id: \.self) { briefing in
Text("hello")
}
}
Note that the result of your update method is of type [[Briefing]], which means the parameter in the ForEach is section: [Briefing] (and not Briefing):
var body: some View {
let data: [[Briefing]] = update(self.fetchRequest.wrappedValue)
return List {
ForEach(data, id: \.self) { (section: [Briefing]) in
Section(header: Text("")) { // <- can't be `section.category!`
self.categoryView(section: section)
}
}
}
}
This also means you can't write section.category! in the header as the section is an array.
You may need to access a Briefing object to get a category:
Text(section[0].category!)
(if you're sure the first element exists).
For clarity I specified types explicitly. It's also a good way to be sure you always use the right type.
let data: [[Briefing]] = update(self.fetchRequest.wrappedValue)
However, Swift can infer types automatically. In the example below, the data will be of type [[Briefing]]:
let data = update(self.fetchRequest.wrappedValue)

Error: Initializer 'init(_:)' requires that 'Binding<String>' conform to 'StringProtocol'

I am getting the above error and couldn't figure out how to solve it. I have an array of objects that contain a boolean value, and need to show a toggle for each of these boolean.
Below is the code.
class Item: Identifiable {
var id: String
var label: String
var isOn: Bool
}
class Service: ObservableObject {
var didChange = PassthroughSubject<Void, Never>()
var items: [Item] {
didSet {
didChange.send(())
}
}
}
struct MyView: View {
#ObservedObject var service: Service
var body: some View {
List {
ForEach(service.items, id: \.self) { (item: Binding<Item>) in
Section(header: Text(item.label)) { // Error: Initializer 'init(_:)' requires that 'Binding<String>' conform to 'StringProtocol'
Toggle(isOn: item.isOn) {
Text("isOn")
}
}
}
}
.listStyle(GroupedListStyle())
}
}
Use the #Published property wrapper in your Service class, rather than didChange, and iterate over the indices of service.items like so:
struct Item: Identifiable {
var id: String
var label: String
var isOn: Bool {
didSet {
// Added to show that state is being modified
print("\(label) just toggled")
}
}
}
class Service: ObservableObject {
#Published var items: [Item]
init() {
self.items = [
Item(id: "0", label: "Zero", isOn: false),
Item(id: "1", label: "One", isOn: true),
Item(id: "2", label: "Two", isOn: false)
]
}
}
struct MyView: View {
#ObservedObject var service: Service
var body: some View {
List {
ForEach(service.items.indices, id: \.self) { index in
Section(header: Text(self.service.items[index].label)) {
Toggle(isOn: self.$service.items[index].isOn) {
Text("isOn")
}
}
}
}
.listStyle(GroupedListStyle())
}
}
Update: Why use indices?
In this example, we need to get two things from each Item in the model:
The String value of the label property, to use in a Text view.
A Binding<Bool> from the isOn property, to use in a Toggle view.
(See this answer where I explain Binding.)
We could get the label value by iterating over the items directly:
ForEach(service.items) { (item: Item) in
Section(header: Text(item.label)) {
...
}
But the Item struct does not contain a binding. If you tried to reference Toggle(isOn: item.$isOn), you'd get an error: "Value of type 'Item' has no member '$isOn'."
Instead, the Binding is provided at the top level by the #ObservedObject property wrapper, meaning the $ has to come before service. But if we're starting from service, we'll need an index (and we cannot declare intermediate variables inside the ForEach struct, so we'll have to compute it inline):
ForEach(service.items) { (item: Item) in
Section(header: Text(item.label)) {
Toggle(isOn: self.$service.items[self.service.items.firstIndex(of: item)!].isOn) {
// This computes the index ^--------------------------------------^
Text("isOn")
}
}
}
Oh, and that comparison to find the index would mean Item has to conform to Equatable. And, most importantly, because we are looping over all items in the ForEach, and then again in the .firstIndex(of:), we have transformed our code from O(n) complexity to O(n^2), meaning it will run much more slowly when we have a large number of Items in the array.
So we just use the indices. Just for good measure,
ForEach(service.items.indices, id: \.self) { index in
is equivalent to
ForEach(0..<service.items.count, id: \.self) { index in