issue with list in swift (Xcode 13.0) - swift

import SwiftUI
struct HomeView: View {
var categories:[String:[Sticker]] {
.init(
grouping: stickerData, by: {$0.category.rawValue}
)
}
var body: some View {
NavigationView{
//Error in this line
List (categories.keys.sorted().identified(by: \String.self)){ key in
StickerRow(categoryName: " Sticker \(key)".uppercased(), stickers: self.categories[key]!)
}
}
}
}
it display this error but I don't know what to do, any suggestions?
Value of type '[Dictionary<String, [Sticker]>.Keys.Element]' (aka 'Array') has no member 'identified'
Sticker.swift code
import Foundation
import SwiftUI
struct Sticker: Hashable, Codable, Identifiable {
var id:Int
var name:String
var imageName:String
var category:Category
var description:String
enum Category: String, CaseIterable, Hashable, Codable {
case first = "first"
case second = "second"
}
}

Instead of using identified, you can use id as below:
List(categories.keys.sorted(), id: \.self) { key in
StickerRow(categoryName: " Sticker \(key)".uppercased(), stickers: self.categories[key]!)
}

Related

SwiftUI TextField Binding to a simple Model (non-class sturct)

I have a simple struct, that is decodable / codable and hashable.
public struct Field: Codable, Hashable {
let key: String
enum CodingKeys: String, CodingKey {
case key
}
}
In my SwiftUI, I'm creating an array, where I want to add and remove and Bind to the Struct values. But I am getting errors, that Struct is not Binding.
let decodableJSON = """
{
"key": ""
}
"""
let settableInit: Field = try! JSONDecoder().decode(Field.self, from: decodableJSON.data(using: .utf8)!)
struct Test_view: View {
#State
var settableFields: [Field] = [settableInit]
var body: some View {
ForEach(settableFields, id: \.key) { (settableField: Field) in
TextField("Key", text: settableField.key)
}
But I get an error, that settableField is not Binding. I have tried adding the settableInit as an #ObservableObject to the main Swift View, but it still doesn't work.
Is there a way, to have the View bind to Struct properties, and have TextField change these properties? It feels like something so trivial, but for some reason undoable for me.
Thank you for any pointers!
In Xcode13 you can use the new element binding syntax:
public struct Field: Codable, Hashable {
var key: String
enum CodingKeys: String, CodingKey {
case key
}
}
struct Demo: View {
#State var settableFields: [Field] = [Field(key: "1"), Field(key: "2")]
var body: some View {
ForEach($settableFields, id: \.key) { $settableField in
TextField("Key", text: $settableField.key)
}
}
}
In earlier versions of Xcode you could use the indices of the array:
struct Demo: View {
#State var settableFields: [Field] = [Field(key: "1"), Field(key: "2")]
var body: some View {
ForEach(settableFields.indices, id: \.self) { index in
TextField("Key", text: $settableFields[index].key)
}
}
}

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())")
}

How to append an identifiable struct list?

struct Gg: Identifiable{
let id: Int
let task: String
}
struct ContentView: View {
#State private var items = [Gg(id: 1, task:"take the trash out"), Gg(id: 2, task:"Go for a run")]
var body: some View {
AddTaskUIView(title: "Add Item", isShown: $isPresented, text: $text, onDone: { text in
self.items.append(text)
self.text = ""
})
}}
I am getting an error
Cannot convert value of type 'String' to expected argument type 'Gg'
how to I append to the task of the structure Gg?
I am new to swift so I would appriacte any help thanks.
struct Gg: Identifiable{
let id: UUID = UUID()
let task: String
init(_ task: String) {
self.task = task
}
}
...
#State private var items = [Gg("take the trash out"), Gg("Go for a run")]
...
self.items.append(Gg(text))
Heres a solution that does not have the problem of keeping track of the id yourself.

Cannot convert value of type 'String' to expected argument type 'Binding<String>'

I am new to SwiftUi and I got an Error which I can not fix.
Basically, I want to change the name attribute of the AB class in the SettingsView.
Also, I got some questions which I hope anyone can answer.
Do i have to make the class AB an ObservableObject with #Published attributes, when it is already in my User class as #Published attribute?
Should the class AB be a struct? I am using the class User as an EnvironmentObject
class User: ObservableObject {
#Published var name: String
...
#Publsihed var ab: [AB]
#Published var currentAb: AB?
internal init(name: String, ab: [AB]) {
self.name = name
self.ab = ab
self.currentAb = ab.first
}
}
class AB: ObervableObject {
#Published var name: String
...
}
I get the Error here because of TextField("new name", text: $user.currentAb.wrappedValue.name).
struct SettingsView: View {
#EnvironmentObject var user: User
var body: some View {
Form { //Error: Unable to infer complex closure return type; add explicit type to disambiguate
Section(header: Text("")) {
TextField("new name", text: $user.currentAb.wrappedValue.name) // <- Error is shown here
.textFieldStyle(RoundedBorderTextFieldStyle())
}
}
}
Thanks.
It is better to do by separating into different view, like
var body: some View {
Form {
Section(header: Text("")) {
if user.currentAb != nil {
SomeNameView(vm: user.currentAb!)
} else {
Text("any holder view here")
}
}
}
}
and separated view
struct SomeNameView: View {
#ObservedObject var vm: AB
var body: some View {
TextField("new name", text: $vm.name)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
}

Cannot convert value of type 'UUID' to expected argument type 'KeyPath<Item, ID>' in SwiftUI

I'm implementing my own custom Grid in SwiftUI. I'm using generics to lay out an array of given items in a Grid fashion. I want them to be animated so I need to specify an id for ForEach within Grid implementation. How to specify id since items are generics.
I thought of using UUID I got the following error
Cannot convert the value of type 'UUID' to expected argument type
'KeyPath<Item, ID>'
Also, I tried id: \.self - I'm getting following error:
Generic struct 'ForEach' requires that 'Item' conform to 'Hashable'
Here is my Grid code:
struct Grid<Item, ItemView> : View where Item : Identifiable, ItemView : View{
private var items : Array<Item>
private var viewForItem : (Item) -> ItemView
init(_ items: Array<Item>, viewForItem: #escaping (Item) -> ItemView) {
self.items = items
self.viewForItem = viewForItem
}
func getLayout(itemsCount: Int, size: CGSize) -> GridLayout {
GridLayout(itemCount: itemsCount, in: size)
}
var body: some View {
GeometryReader { geometry in
ForEach(self.items, id: \.self) { item in
self.viewForItem(item)
.frame(width: self.getLayout(itemsCount: self.items.count, size: geometry.size).itemSize.width,
height: self.getLayout(itemsCount: self.items.count, size: geometry.size).itemSize.height)
.position(self.getLayout(itemsCount: self.items.count, size: geometry.size)
.location(ofItemAt: self.items.firstIndex(of: item)!))
}
}
}
}
Here is my model:
struct SetGame {
var deck : Array<SetCard> = []
var playingTwelveDeck : Array<SetCard> = []
}
struct SetCard : Identifiable, Hashable {
var color : ColorValue
var shapeName : ShapeName
var shading : Shadings
var count : Int
var isSelected = false
var isMatched = false
var position : Int
var isSelectedAnimationToggle : Bool
var id = UUID()
}
Im using SetGame.SetCard as my item in Grid.
If you want to constrain Item to Hashable you can do:
struct Grid<Item, ItemView>: View
where Item: Identifiable & Hashable, // <- add `Hashable`
ItemView: View
{ ... }