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.
Related
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 {
I have a ListView that is a few levels deep in a NavigationView. The idea is when a user selects an item in the ListView, the item is stored and the UI pops back to the root NavigationView.
Specific Implementation (Working)
class SpecificListItem: ObservableObject, Codable, Hashable, Identifiable {
var id: String
var kind: String
var name: String
var mimeType: String
init(id: String, kind: String, name: String, mimeType: String) {
self.id = id
self.kind = kind
self.name = name
self.mimeType = mimeType
}
static func == (lhs: DriveListItem, rhs: DriveListItem) -> Bool {
return lhs.id == rhs.id &&
lhs.kind == rhs.kind &&
lhs.name == rhs.name &&
lhs.mimeType == rhs.mimeType
}
func hash(into hasher: inout Hasher) {
hasher.combine(self.id)
}
func processItem() {
// do backend stuff
}
}
...
struct SpecificListView: View {
#EnvironmentObject var selectedItem: SpecificListItem
// load excluded for brevity
#ObservedObject var item: MyResponse<SpecificListResponse>
var body: some View {
List(selection: $selectedItem, content: {
ForEach(item.data?.files ?? []) {
file in HStack {
Text(file.name)
}
.onTapGesture {
// pop to root view
}
}
})
}
}
This was good for a proof of concept. Now I need to add a layer of abstraction.
Attempt at Generic Implementation
The selected item in this view should be generic. I have the following protocol and class defined that the selected item should conform to:
protocol SelectableItem: ObservableObject, Identifiable, Hashable, Codable {
var id: String { get set }
func process()
}
class GenericListItem: SelectableItem {
var id: String = ""
var name: String = ""
init () { }
init(id: String) {
self.id = id
}
static func == (lhs: GenericListItem, rhs: GenericListItem) -> Bool {
return lhs.id == rhs.id
}
func hash(into hasher: inout Hasher) {
hasher.combine(self.id)
}
func process() {
// do backend stuff
}
}
class SpecificListItem: GenericListItem {
var type: String
init () { }
init(id: String) {
self.id = id
}
override func process() {
// do backend stuff
}
}
...
struct GenericListView: View {
#EnvironmentObject var selectedItem: GenericListItem
// load excluded for brevity
#ObservedObject var item: MyResponse<SpecificListItem>
var body: some View {
List(selection: $selectedItem, content: {
ForEach(item.data?.files ?? []) {
file in HStack {
Text(file.name)
}
.onTapGesture {
// pop to root view
}
}
})
}
}
The selected item can have it's own data elements, but I am at least expecting each item to have an id and a process() method that interacts with my backend.
However, when I try to bind the selectedItem in the ListView, I get the following error: Generic parameter 'SelectionValue' could not be inferred.
I am assuming this is because the types do not match. I have read there isn't really any dynamic or run-time type binding possible in Swift? Is this true?
I am a little more experienced in Java, and I know I could achieve this through generics and polymorphism.
SpecificListItem IS-A GenericListItem yet Swift doesn't seem to like the design. (And MyResponse has-a SpecificListItem).
How can I define a generic class that can be selected from many child views and bound at the root view, yet can be inherited to implement their own specific logic?
Edit
My abstraction layer explanation was unclear if not just wrong. So I tried to fix that.
Adding some more information surrounding the code structure and design. Maybe it will help give some more background and context on what I am trying to accomplish.
Here is a small representation of the object interactions:
The idea is I will have many detail views in the application. The backend will return various response types, all conforming to SelectableItem. In this case, the detail view item source is of type SpecificListItem.
And here is the interaction between the user, backend, and UI:
And the idea here being:
User navigates to Detail View
Backend call is made, returns a list of SpecificListItem
List View is populated with items of type SpecifcListItem.
When a user selects an item from Detail View, the application pops back to root, and the selectedItem is set in the root view which is of type GenericListItem.
I have a problem programmatically opening and closing a View in SwiftUI:
With the code below SwiftUI opens each index of contactsArray one after another, when clicking on one of them (it loops through all of them). Of course it should just open the one I clicked on.
I thought the problem might rely on the id but my Model is Identifiable.
ContactsView:
// ...
List {
ForEach(contactsViewModel.contactsArray, id: \.self) {
contact in
NavigationLink(destination: ContactsDetailsView(contact: contact), isActive: self.$userViewModel.showContacts) {
Text(contact.surname).bold() + Text(", ") + Text(contact.forename)
}
}
}
ContactsViewModel:
final class ContactsViewModel: ObservableObject {
#Published var contactsArray: [ContactModel] = []
// ...
}
ContactModel:
struct ContactModel: Decodable, Identifiable, Equatable, Hashable, Comparable {
var id: String
var surname: String
var forename: String
var telephone: String
var email: String
var picture: String
var gender: String
var department: String
static func < (lhs: ContactModel, rhs: ContactModel) -> Bool {
if lhs.surname != rhs.surname {
return lhs.surname < rhs.surname
} else {
return lhs.forename < rhs.forename
}
}
static func == (lhs: ContactModel, rhs: ContactModel) -> Bool {
return lhs.surname == rhs.surname && lhs.forename == rhs.forename
}
}
You join all NavigationLink/s to one state (one-to-many), so, no surprise, when you toggle this state all links are activated.
Instead you need something like the following
#State private var selectedContact: String? = nil // or put it elsewhere
...
NavigationLink(destination: ContactsDetailsView(contact: contact),
tag: contact.id, selection: $selectedContact) {
Text(contact.surname).bold() + Text(", ") + Text(contact.forename)
}
, where selectedContact is id of contact link to be activated. Then all you need is to decide which contact id to assign to selectedContact.
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
}
}
I have a parent class, that has children (Child Class), that have Puppets (Puppet Class).
The data is displayed in a list of parents that navigates to a list of children, that navigates to a list of puppets.
Within the views, the user is able to add parents, children and puppets or remove them.
I want to store the data locally so that every change is save. I think storing the Parent class is enough, because of parents.children.puppets.
Thus I need to conform to Codable anyhow and decode and encode my data anyhow.
The AppState should load local data or [] instead of the current dummy data. On every parent (or child or puppet) change, I want to store the parents locally in the most efficient way.
Same for receiving the data.
class Parent: ObservableObject, Hashable {
static func == (lhs: Parent, rhs: Parent) -> Bool {
lhs.id == rhs.id
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
let id = UUID()
let name: String
#Published var children: [Child]
init(name: String, children: [Child]? = nil) {
self.name = name
self.children = children ?? []
}
func remove(child: Child) {
self.children.remove(child)
}
func add(child: Child) {
self.children.append(child)
}
}
class Child: ObservableObject, Identifiable, Hashable {
static func == (lhs: Child, rhs: Child) -> Bool {
return lhs.id == rhs.id
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
let id = UUID()
let name: String
#Published var puppets: [Puppet]
init(name: String, puppets:[Puppet]? = nil) {
self.name = name
self.puppets = puppets ?? []
}
func remove(puppet: Puppet) {
self.puppets.remove(puppet)
}
func add(puppet: Puppet) {
self.puppets.append(puppet)
}
}
struct Puppet: Identifiable, Hashable {
let id = UUID()
let name: String
}
class AppState: ObservableObject {
#Published var parents: [Parent]
init() {
self.parents = [
Parent(name: "Foo", children: [Child(name: "bar", puppets: [Puppet(name: "Tom")])]),
Parent(name: "FooBar", children: [Child(name: "foo", puppets: nil)])
]
}
}
extension Array where Element: Identifiable {
mutating func remove(_ object: Element) {
if let index = self.firstIndex(where: { $0.id == object.id}) {
self.remove(at: index)
}
}
}
I like very much pawello2222's detailed response. If you continue to develop on iOS, you will eventually need to learn CoreData.
That said, if you don't have time to do that for your current project, personally, I would recommend using RealmSwift and creating Realm Parent, Child and Puppet objects. It would take you an hour to learn how to install and use RealmSwift.
Here's a start:
import RealmSwift
class Puppet: Object {
#objc dynamic var id = UUID()
#objc dynamic var name: String = ""
}
But if you want to save a small number of objects you could use UserDefaults - none of your objects are using unsupported types, so just declare them as codable and let the compiler do the conversion automatically.
Conforming class to codable protocol in swift4
It is quite straightforward. 🍀
If you're not bound to any database or format, I'd recommend using CoreData (as suggested in the comments).
From Apple documentation:
Use Core Data to save your application’s permanent data for offline
use, to cache temporary data, and to add undo functionality to your
app on a single device.
This way your model doesn't have to conform to Codable.
You can find more information in these links:
Core Data for beginners
Getting started with Core Data Tutorial
Getting started with Core Data