I have the following code which represents a Hockey Stick and some information about it. I have an issue where the stick isn't conforming to Decodable. I understand that every type used in the struct needs to also be codeable, and they are. For some reason however the "var conditions" line causes the error that I am unsure how to fix. Thank you!
enum StickLocation: Int, Codable, Hashable, CaseIterable {
case handle, mid, bottom
}
enum StickCondition: Int, Codable, Hashable, CaseIterable {
case pristine, scuffed, damaged, broken
}
struct HockeyStick: Identifiable, Codable {
var barcode: Int
var brand: String
var conditions: [StickLocation:(condition:StickCondition, note:String?)] // Offending line
var checkouts: [CheckoutInfo]
var dateAdded: Date
var dateRemoved: Date?
// Conform to Identifiable.
var id: Int {
return self.barcode
}
// If the stick was never removed then its in service.
var inService: Bool {
return self.dateRemoved == nil
}
}
The value type of your conditions dictionary is (StickCondition, String?), which is a tuple. Tuples are not Decodable/Encodable, and you cannot make them conform to protocols, so to fix this I recommend you make a new struct to replace the tuple like this:
enum StickLocation: Int, Codable, Hashable, CaseIterable {
case handle, mid, bottom
}
enum StickCondition: Int, Codable, Hashable, CaseIterable {
case pristine, scuffed, damaged, broken
}
struct StickConditionWithNote: Codable, Hashable {
var condition: StickCondition
var note: String?
}
struct HockeyStick: Identifiable, Codable {
var barcode: Int
var brand: String
var conditions: [StickLocation: StickConditionWithNote]
var checkouts: [CheckoutInfo]
var dateAdded: Date
var dateRemoved: Date?
// Conform to Identifiable.
var id: Int {
return self.barcode
}
// If the stick was never removed then its in service.
var inService: Bool {
return self.dateRemoved == nil
}
}
Related
The line VStack shows error "Instance method 'sheet(item:onDismiss:content:)' requires that 'Int' conform to 'Identifiable'" but i think i dont make any mistake there? did i?
It feel like error come from my sheet complaining my Int should conform to 'Identifiable' however is not Int is an 'Identifiable' since it's primitive? how would i solve this problem?
struct SheetView: View {
#State var bindingItemDiscounts: [Double] =
[ 0.99, 0.25, 0.31, 11.99, 19.99, 0.1, 0.5]
#State var discountMoreThanTen: Int?
var body: some View {
VStack(alignment: .leading) { <- this line
Button {
discountMoreThanTen = bindingItemDiscounts.count
} label: { Text("Check Discounts") }
}.sheet(item: $discountMoreThanTen) { newValue in
if discountMoreThanTen ?? 0 <= 10 {
List(bindingItemDiscounts, id: \.self) { discount in
Text("Discount : \(discount)")
}
}
}
}
}.
There is no such concept as Primitive in Swift. The "Primitives" you refer to are normal structs.
Int does not conform to Identifiable.
To fix your problem just extend Int to conform to Identifiable like so:
extension Int: Identifiable {
public var id: Int { return self }
}
You are using the Binding<Item?> sheet. The Item is a type, so it must conform to Identifiable in order to be identified uniquely.
Also, Int is not a primitive in Swift. Int is basically a Type just like your other custom data types, so you need to make it Identifiable manually.
Add this extension in your file will fix this problem:
extension Int: Identifiable {
public var id: Self { self }
}
If UUID() is required, then use this instead:
extension Int: Identifiable {
public var id: String {
return UUID().uuidString
}
}
I am getting the following error when I try to compare values in the task modifier.
Operator function '==' requires that 'Binding' conform to 'Equatable'
How can I make Binding confirm to Equatable?
The non-binding ordinary NotificationDetail already confirms to Equatable.
#ObservedObject var notificationsViewModel = NotificationsViewModel.shared
//NotificationsViewModel does a API call and puts the fetched data in the Notifications Model
var body: some View {
VStack {
ForEach($notificationsViewModel.notifications.notificationsDetails) { $notificationsDetail in
VStack(alignment: .leading) {
if notificationsDetail.notificationsCategoriesId == 2 {
NotificationsFriendRequestCell(notificationsDetail: $notificationsDetail, viewModel: viewModel, notificationsViewModel: notificationsViewModel, tabSelection: $tabSelection) //ReceiveFriendRequestCell(user: user, viewModel: viewModel)
}
else if notificationsDetail.notificationsCategoriesId == 3 {
NotificationsFriendRequestApprovedCell(notificationsDetail: $notificationsDetail, viewModel: viewModel, notificationsViewModel: notificationsViewModel, tabSelection: $tabSelection)
}
}
.task { //task modifier for infinite scrolling
//BELOW LINE GETTING ERRROS
if $notificationsDetail == $notificationsViewModel.notifications.notificationsDetails.last {
print("task modifier last item")
numberOfSkips += 10
notificationsViewModel.getMyNotifications(isOnlyUnread: false, skip: numberOfSkips, limit: 10)
}
}
} //ForEach end
} //VStack end
Model
struct Notifications: Codable, Identifiable {
let id = UUID()
let numberOfNotifications: Int
var notificationsDetails: [NotificationsDetail]
enum CodingKeys: String, CodingKey {
case numberOfNotifications = "number_of_notifications"
case notificationsDetails = "notifications"
}
}
struct NotificationsDetail: Codable, Identifiable, Equatable {
let id: Int
let notificationsCategoriesId: Int
let questionsUsersName: String?
enum CodingKeys: String, CodingKey {
case id = "notifications_id"
case notificationsCategoriesId = "notifications_categories_id"
case questionsUsersName = "questions_users_name"
}
static var example = NotificationsDetail(id: 0, notificationsCategoriesId: 1, questionsId: 1, questionsUsersName: "")
init(id: Int, notificationsCategoriesId: Int, questionsUsersName: String?) {
self.id = id
self.notificationsCategoriesId = notificationsCategoriesId
self.questionsUsersName = questionsUsersName
}
}
ViewModel
class NotificationsViewModel: ObservableObject {
static let shared = NotificationsViewModel()
#Published var notifications: Notifications = Notifications.example
init(){
A()
}
func A () {
//do API calls and put data in #Published var notifications
}
I tried tweaking the values by removing or adding $ mark but get other errors...
if notificationsDetail == notificationsViewModel.notifications.notificationsDetails.last
//Error: Reference to captured var 'notificationsDetail' in concurrently-executing code
if $notificationsDetail == notificationsViewModel.notifications.notificationsDetails.last
//Error: Binary operator '==' cannot be applied to operands of type 'Binding<[NotificationsDetail]>.Element' (aka 'Binding<NotificationsDetail>') and '((NotificationsDetail) throws -> Bool) throws -> NotificationsDetail?'
Any advice would be highly appreciated!
Thanks in advance.
The reason for the error is you are handing your closure a binding through your ForEach, but you want to compare to the value of this binding. Accessing it via $ and .wrappedValue should work here. So change your code to:
if $notificationsDetail.wrappedValue == notificationsViewModel.notifications.notificationsDetails.last
For structs, Swift auto synthesizes the hashValue for us. So I am tempting to just use it to conform to the identifiable.
I understand that hashValue is many to one, but ID must be one to one. However, hash collision is very rare and I have faith in the math that it's most likely never going to happen to my app before I die.
I am wondering is there any other problems besides collision?
This is my code:
public protocol HashIdentifiable: Hashable & Identifiable {}
public extension HashIdentifiable {
var id: Int {
return hashValue
}
}
using hashValue for id is a bad idea !
for example you have 2 structs
struct HashID: HashIdentifiable {
var number: Int
}
struct NormalID: Identifiable {
var id = UUID()
var number: Int
}
when number is changed:
HashID's id will be changed as well makes SwiftUI thinks that this is completely new item and old one is gone
NormalID's id stays the same, so SwiftUI knows that item only modified its property
It's very important to let SwiftUI knows what's going on, because it will affect animations, performance, ... That's why using hashValue for id makes your code looks bad and you should stay away from it.
I am wondering is there any other problems besides collision?
Yes, many.
E.g., with this…
struct ContentView: View {
#State private var toggleData = (1...5).map(ToggleDatum.init)
var body: some View {
List($toggleData) { $datum in
Toggle(datum.display, isOn: $datum.value)
}
}
}
Collisions cause complete visual chaos.
struct ToggleDatum: Identifiable & Hashable {
var value: Bool = false
var id: Int { hashValue }
var display: String { "\(id)" }
init(id: Int) {
// Disregard for now.
}
}
No collisions, with unstable identifiers, breaks your contract of "identity" and kills animation. Performance will suffer too, but nobody will care about that when it's so ugly even before they notice any slowness or battery drain.
struct ToggleDatum: Identifiable & Hashable {
var value: Bool = false
var id: Int { hashValue }
let display: String
init(id: Int) {
self.display = "\(id)"
}
}
However, while it is not acceptable to use the hash value as an identifier, it is fine to do the opposite: use the identifier for hashing, as long as you know the IDs to be unique for the usage set.
/// An `Identifiable` instance that uses its `id` for hashability.
public protocol HashableViaID: Hashable, Identifiable { }
// MARK: - Hashable
public extension HashableViaID {
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
struct ToggleDatum: HashableViaID {
var value: Bool = false
let id: Int
var display: String { "\(id)" }
init(id: Int) {
self.id = id
}
}
This protocol works perfectly for Equatable classes, as classes already have a default ID ready for use.
extension Identifiable where Self: AnyObject {
public var id: ObjectIdentifier {
return ObjectIdentifier(self)
}
}
Not that I at all recommend using a reference type for this example, but it would look like this:
public extension Equatable where Self: AnyObject {
static func == (class0: Self, class1: Self) -> Bool {
class0 === class1
}
}
class ToggleDatum: HashableViaID {
It's probably a stupid question but I'm trying to allow all string enums as type for a variable in a Struct. The code below is completely wrong but I hope that it will make my question clearer.
My idea was to conform all enums to the same protocol but I can't access .allCases with this approach.
My goal is that I can pass any string enum to the ListView which will then display all components of the enum (here: one; two; three).
Does anybody have an idea how to do this? It must be a very basic Swift thing but I wasn't able to figure it out searching through the web. Thank you so much!
import SwiftUI
struct ContentView: View {
var body: some View {
ListView(myEnum: Elements.self) //error here as well
}
}
protocol StringRepresentable {
var rawValue: String { get }
}
enum Elements: String, Equatable, CaseIterable, StringRepresentable {
case one
case two
case three
}
struct ListView: View {
let myEnum: StringRepresentable //doesn't work
var body: some View {
ForEach(myEnum.allCases, id: \.self) { elem in //can't access .allCases
Text(elem.rawValue)
}
}
}
There's several errors in your original code. First you weren't using a VStack (or List or LazyVStack) so your foreach would only render one element.
Secondly, you were passing the type, not the elements itself to the list view. And finally, your own StringRepresentable protocol is unnecessary, you can use RawRepresentable with it's associated type RawValue constrained to String
i.e. something like this:
struct ContentView: View {
var body: some View {
VStack {
ListView(values: Fruits.allCases)
ListView(values: Animals.allCases)
}
}
}
enum Fruits: String, CaseIterable {
case apple
case orange
case banana
}
enum Animals: String, CaseIterable {
case cat
case dog
case elephant
}
struct ListView<T: RawRepresentable & Hashable>: View where T.RawValue == String {
let values: [T]
var body: some View {
LazyVStack {
ForEach(values, id: \.self) { elem in
Text(elem.rawValue)
}
}
}
}
Which renders like this
Here is a variant that will work by sending in all items of an enum to the view rather that the enum itself and making the view generic.
struct ContentView: View {
var body: some View {
ListView(myEnum: Elements.allCases)
}
}
protocol StringRepresentable {
var rawValue: String { get }
}
enum Elements: String, Equatable, CaseIterable, StringRepresentable {
case one
case two
case three
}
struct ListView<T: CaseIterable & StringRepresentable>: View {
let myEnum: [T]
#State private var selectedValue: String = ""
var body: some View {
ForEach(0..<myEnum.count) { index in
Text(myEnum[index].rawValue)
.onTapGesture {
selectedValue = myEnum[index].rawValue
}
}
}
}
I have a searchController, when I try to delete the text in searchController it crash the message is.
supplied identifiers are not unique
I try to fix it with hasher function like this and make my model Hashable, and Equatable. but still not fix the problem, please help. this is my code, if there is something not clear in my code. feel free to mention me I will update it :)
struct PhotosResults: Decodable {
var total, totalPages: Int
var results: [PhotoItem]
}
// MARK: - Result
struct PhotoItem: Decodable {
var id: String
var createdAt: String
var width, height: Int
var color: String
var likes: Int
var likedByUser: Bool
var description: String?
var user: User
var urls: Urls
}
extension PhotoItem: Hashable, Equatable {
static func == (lhs: PhotoItem, rhs: PhotoItem) -> Bool {
return lhs.id == rhs.id
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
// MARK: - Urls
struct Urls: Decodable {
var raw, full, regular, small: String
var thumb: String
}
// MARK: - User
struct User: Decodable {
var id, username, name, firstName: String
var lastName, instagramUsername, twitterUsername: String?
var portfolioUrl: String?
var profileImage: ProfileImage
}
extension User: Hashable, Equatable {
static func == (lhs: User, rhs: User) -> Bool {
return lhs.id == rhs.id
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
// MARK: - ProfileImage
struct ProfileImage: Decodable {
var small, medium, large: String
}
Base on Apple Documentation:
Hash values are not guaranteed to be equal across different executions
of your program. Do not save hash values to use during a future
execution.
Although you have conformed to hashable somehow it is possible that two objects have the same id and in this situation, you will get an error so in order to make sure all objects are unique you must add a UUID to your desired struct For Example the PhotoItem should look like this:
struct PhotoItem: Decodable {
let uuid = UUID()
var id: String
var createdAt: String
var width, height: Int
var color: String
var likes: Int
var likedByUser: Bool
var description: String?
var user: User
var urls: Urls
}
extension PhotoItem: Hashable {
func hash(into hasher: inout Hasher) {
hasher.combine(uuid)
}
static func ==(lhs: PhotoItem, rhs: PhotoItem) -> Bool {
return lhs.uuid == rhs.uuid
}
}
you can use this solution for other structs. Hashable conforms to Equatable by default so you don't need to conform to it again.
Note: I was accidentally loading my data twice. I only got this when I scrolled back over the cell containing the id's, not first collectionview snapshot apply. So if yours only is doing it sometimes, make sure you are only loading your cells on the initial load.