How to conform an enumeration to Identifiable protocol in Swift? - ios

I'm trying to make a list with the raw values of the cases from an enumeration with the new SwiftUI framework. However, I'm having a trouble with conforming the 'Data' to Identifiable protocol and I really cannot find information how to do it. It tells me "Initializer 'init(_:rowContent:)' requires that 'Data' conform to 'Identifiable'" The stub provides me with an ObjectIdentifier variable in the last extension, but don't know what should I return. Could you tell me how do it? How do I conform Data to Identifiable, so I can make a list with the raw values?
enum Data: String {
case firstCase = "First string"
case secondCase = "Second string"
case thirdCase = "Third string"
}
extension Data: CaseIterable {
static let randomSet = [Data.firstCase, Data.secondCase]
}
extension Data: Identifiable {
var id: ObjectIdentifier {
return //what?
}
}
//-------------------------ContentView------------------------
import SwiftUI
struct Lala: View {
var name: String
var body: some View {
Text(name)
}
}
struct ContentView: View {
var body: some View {
return List(Data.allCases) { i in
Lala(name: i.rawValue)
}
}
}

⚠️ Try not to use already used names like Data for your internal module. I will use MyEnum instead in this answer
When something conforms to Identifiable, it must return something that can be identified by that. So you should return something unique to that case. For String base enum, rawValue is the best option you have:
extension MyEnum: Identifiable {
var id: RawValue { rawValue }
}
Also, enums can usually be identified by their selves:
extension MyEnum: Identifiable {
var id: Self { self }
}
⚠️ Note 1: If you return something that is unstable, like UUID() or an index, this means you get a new object each time you get the object and this will kill reusability and can cause epic memory and layout process usage beside view management issues like transition management and etc.
Take a look at this weird animation for adding a new pet:
Note 2: From Swift 5.1, single-line closures don't need the return keyword.
Note 3: Try not to use globally known names like Data for your own types. At least use namespace for that like MyCustomNameSpace.Data
Inline mode
You can make any collection iterable inline by one of it's element's keypath:
For example to self:
List(MyEnum.allCases, id:\.self)
or to any other compatible keypath:
List(MyEnum.allCases, id:\.rawValue)
✅ The checklist of the identifier: (from WWDC21)
Exercise caution with random identifiers.
Use stable identifiers.
Ensure the uniqueness, one identifier per item.

Another approach with associated values would be to do something like this, where all the associated values are identifiable.
enum DataEntryType: Identifiable {
var id: String {
switch self {
case .thing1ThatIsIdentifiable(let thing1):
return thing1.id
case .thing2ThatIsIdentifiable(let thing2):
return thing2.id
}
}
case thing1ThatIsIdentifiable(AnIdentifiableObject)
case thing2ThatIsIdentifiable(AnotherIdentifiableObject)

You can try this way:
enum MyEnum: Identifiable {
case valu1, valu2
var id: Int {
get {
hashValue
}
}
}

Copyright © 2021 Mark Moeykens. All rights reserved. | #BigMtnStudio
Combine Mastery in SwiftUI book
enum InvalidAgeError: String, Error , Identifiable {
var id: String { rawValue }
case lessThanZero = "Cannot be less than zero"
case moreThanOneHundred = "Cannot be more than 100"
}

Related

Protocols and Enums in Swift with Apollo

I am using Apollo for Swift in an iOS app. I have multiple types that all represent the same object. These types are auto-generated from a schema file and look something like this.
struct CurrentUser {
var id: String
...
}
struct MyUser {
var id: String
...
}
Basically Apollo generates multiple Swift types (one for each query) for the same underlying data type.
I want to create a new struct that unifies these types.
I would like to do something like this:
protocol UserProtocol {
var id: String { get }
}
struct User {
var id: String
...
init(_ data: UserProtocol) {
self.id = data.id
...
}
}
This approach however gives me an error when I try to construct a user object, telling me that "Type MyUser does not conform to UserProtocol". If I try to coerce the type with data as! UserProtocol I get a crash.
The only solution I've found is the following:
enum UserType {
case .currentUser(CurrentUser)
case .myUser(MyUser)
}
struct User {
var id: String
...
init(_ data: UserType) {
switch data {
case .myUser(let user):
self.id = data.id
...
case .currentUser(let user):
self.id = data.id
...
}
}
}
This approach works, but it leads to a lot of duplicated code in the init function. Is there a better way to do this in Swift?
I suspect the problem is that you need to explicitly conform the Apollo generated types to your protocol:
extension CurrentUser: UserProtocol { }
extension MyUser: UserProtocol { }
Remember that Swift is not duck-typed like some other languages, so a type with member var id: String is not UserProtocol until you declare it as such.
If for some reason you need to do some transformation of the Apollo types to fit the app models in the future, those extensions are a good place to do that, too.

pass different struct type for a function

struct TypeA: Identifiable {
let id: UUID = UUID()
var dataArr: [TypeB]
}
struct TypeB: Identifiable {
let id: UUID = UUID()
var data: Data
}
I have those two structs and I want to pass them to a function like this, the problem is that sometimes I want to use TypeB in function call
and sometime I want to use TypeA, how can I do that?
func getData(typeB: TypeB){
//some code
}
I want to call the function like this at DataVC,
So I can use this data and append it to array for example
let typeB:TypeB?
getData(typeB)
and call the function like this at EditVC,
So I can edit all the array
let typeA:TypeA?
getData(typeA)
You can define an enum with different cases and their associated values like:
enum ABType {
case a(TypeA)
case b(TypeB)
}
so you can implement the function like:
func getData(type: ABType) {
switch type {
case .a(let objectA): print(objectA.dataArr)
case .b(let objectB): print(objectB.data)
}
}
Also, you can fill the function like:
let typeA: TypeA?
...
getData(type: .a(typeA!))
let typeB: TypeB?
...
getData(type: .b(typeB!))

Is there a way to create a binding off a computed array property in an enum at a particular index in SwiftUI?

I'm modelling view state in my viewModel using an enum...
enum ViewState<T> {
case idle
case error(Error)
case loading
case data([T])
I have a computed property to get the data
var data: [T] {
guard case let .data(data) = self else {
return []
}
return data
}
In one of my views I iterate through the data
var dropdownListView: some View {
ForEach(viewModel.state.data.indices, id: \.self) { index in
DropdownView(
viewModel: $viewModel.state.data[index],
isActionSheetPresented: $viewModel.isActionSheetPresented
)
}.eraseToAnyView()
}
I get an error as you can't make a binding from a computed property so make my own custom binding...
ForEach(viewModel.state.data.indicies, id: \.self) { index in
DropdownView(viewModel: Binding<ItemViewModel>(
get: {return viewModel.state.data[index] },
set: { value in
var data = viewModel.state.data
data[index] = value
viewModel.state = .data(data)
},
isActionSheetPresented: $viewModel.isActionSheetPresented
)
}
This works but are there any issues with setting the whole state again in the binding setter (I believe SwiftUI is intelligent enough that this would be efficient) or is there another way to do this here?
On my vision you mixed a state and a data, which are different things. So instead of .data([T]), I would recommend something like .loaded (ie, state) and keep data by standalone #Published var data: [T] property. If that adapted your code will look much more naturally.
Like
ForEach(viewModel.data.indices, id: \.self) { index in
DropdownView(
viewModel: $viewModel.data[index],
isActionSheetPresented: $viewModel.isActionSheetPresented
)
}//.eraseToAnyView() // << you don't need this
}

Error When Using Protocol as a Type in Swift

I'm using Swift and trying to make some collection objects. These collection objects have a backing Dictionary to hold custom objects. For example, an object might be of type Cat and the collection object would be of type Cats. Cats would have a private dictionary containing values of type Cat. I have other types that also need respective collections (each collection type has specific logic for the type it holds).
I created a protocol to ensure each collection has a few common characteristics. These common functions and subscripts are generally passthroughs to the backing dictionary. Here is the protocol:
protocol ObjectDictionaryProtocol {
// These are necessary for generics to work in protocols
typealias Key: Hashable
typealias Value
// MARK: - Properties
var count: Int { get }
var isEmpty: Bool { get }
var keys: LazyMapCollection<Dictionary<Key, Value>, Key> { get }
var values: LazyMapCollection<Dictionary<Key, Value>, Value> { get }
// MARK: - Subscripts
subscript(key: Key) -> Value? { get set }
}
When I go to actually use the protocol as a type, for instance:
var objects: ObjectDictionaryProtocol
or
init(objs: ObjectDictionaryProtocol) {
...
}
I get the error:
Protocol 'ObjectDictionaryProtocol' can only be used as a generic constraint because it has Self or associated type requirements
I've searched around and it looks like the Hashable protocol I'm conforming to for my Key typealias is causing this. What is the best way to get around this? Is there a way to change the protocol such that I don't need the Hashable, or do I need to do something in the class that is using ObjectDictionaryProtocol? Or maybe there's a better way to effectively 'subclass' a Swift Dictionary (quotes because I realize the Dictionary struct cannot be subclassed)?
A protocol with associated types is less a type and more a template for a type. The actual type exists when the protocol's associated types are specified. Thus ObjectDictionaryProtocol 'becomes' a type when you specify:
ObjectDictionaryProtocol<String,Cat>
but the above is not valid Swift...
So you asked... '[is there] a better way to effectively 'subclass' a Swift Dictionary'. You might get by with an extension with something like:
class Cat {}
extension Dictionary where Value : Cat {
func voice (name: Key) {
if let _ = self[name] {
print ("\(name): Meow")
}
}
}
var someCats = Dictionary<String,Cat>()
someCats["Spot"] = Cat()
someCats.voice("Spot")
// Spot: Meow
Or you may need to actually implement the protocol.
class ObjectDiciontary<Key:Hashable, Value> : ObjectDictionaryProtocol {
var backingDictionary = Dictionary<Key,Value>()
// implement protocol
}
var object : ObjectDictionary<String,Cat> = ...
// ...
The reason is because when you're using typealias you're effectivly making your Protocol into a generic protocol as Protocol<T>. Bear with me here, I'll explain why and how to fix it.
The issue here is that Apple has decided to make typealias the keyword for defining associated types. This is fixed in Swift 2.2 (Xcode 7.3)
You can read more about it on https://github.com/apple/swift-evolution/blob/master/proposals/0011-replace-typealias-associated.md
It's being renamed to associatedtype which makes more sense.
This means your protocol has to be adopted, and there define its associated types.
In your case it would look something like
protocol ObjectDictionaryProtocol {
associatedtype Value
}
extension String : ObjectDictionaryProtocol {
associatedtype = Double
}
The init could look like
init<T : ObjectDictionaryProtocol>(objs: ObjectDictionaryProtocol)
or
init<T : ObjectDictionaryProtocol
where T.Value == Double>(objs: ObjectDictionaryProtocol)
Now for typealias Key: Hashable it means that whatever type that is assigned to Key has to conform or be Hashable
This will give you an error that String isn't conforming to ObjectDictionaryProtocol as it can't fulfil the requirements.
protocol ObjectDictionaryProtocol {
associatedtype Value : FloatingPointType
}
extension String : ObjectDictionaryProtocol {
associatedtype = Int
}
Int isn't a FloatingPointType (but Double is)

Convert NSManagedObjects into structs in a "generic" way (Swift)

I have a CoreDataStore class which has two generic placeholders and can be used for each entity type in the model. The idea is that it fetches an NSManagedObject subclass (based on one of the generic types) from the store, converts it into the appropriate object (based on the other generic type) and returns that object.
The purpose of this behaviour is so I'm keeping the Core Data aspects encapsulated and avoiding passing NSManagedObject instances all around the app.
Example potential usage
This is purely how the usage might look to further demonstrate what I am trying to achieve.
let personStore = CoreDataStore<ManagedPerson, Person>()
let personData = personStore.fetchSomeObject() // personData is a value type Person
I have the following code, separated over several files but shown here in a modified fashion for simplicity.
import Foundation
import CoreData
// MARK: - Core Data protocol and managed object
protocol ManagedObjectProtocol { }
class ManagedPerson: NSManagedObject, ManagedObjectProtocol {
var title: String?
}
class ManagedDepartment: NSManagedObject, ManagedObjectProtocol {
var name: String?
}
// MARK: - Simple struct representations
protocol DataProtocol {
typealias ManagedObjectType: ManagedObjectProtocol
init(managedObject: ManagedObjectType)
}
struct Person {
var title: String?
}
struct Department {
var name: String?
}
extension Person: DataProtocol {
typealias ManagedObjectType = ManagedPerson
init(managedObject: ManagedPerson) {
self.title = managedObject.title
}
}
extension Department: DataProtocol {
typealias ManagedObjectType = ManagedDepartment
init(managedObject: ManagedDepartment) {
self.name = managedObject.name
}
}
class CoreDataStore<ManagedObject: ManagedObjectProtocol, DataObject: DataProtocol> {
func fetchSomeObject() -> DataObject {
var managedObject: ManagedObject // fetch an NSManagedObject
// Error here
return DataObject(managedObject: managedObject)
}
}
The error I am receiving is when I try to initialise the struct in fetchSomeObject:
Cannot invoke initializer for type 'DataObject' with an argument list of type '(managedObject: ManagedObject)'
Obviously the compiler can't figure out that the DataObject (which is restricted to types conforming to DataProtocol) can be initialised with a ManagedObject (which is restricted to types conforming to ManagedObjectProtocol) despite it being declared as such in DataProtocol.
Is there any way to achieve this functionality? Additionally is this a reasonable approach or am I completely off the wall with this?
Update
After a bit of digging it seems that Swift generics are invariant which I believe is causing what I'm running into.
Think your CoreDataStore again, for example, CoreDataStore<ManagedPerson, Department> doesn't make any sense. Why not? Because the Department is a DataProtocol without problem, but its corresponding typealias ManagedObjectType is not ManagedPerson.
The reason why your code won't compile is just the same. Here return DataObject(managedObject: managedObject) you can't initialize an DataObject from an armbitary ManagedObject, only a DataObject.ManagedObjectType is acceptable.
So what you need is a type constraint, add this where clause, your code should work:
class CoreDataStore<ManagedObject: ManagedObjectProtocol, DataObject: DataProtocol
where DataObject.ManagedObjectType == ManagedObject>

Resources