Using swift protocols as Equatable argument - swift

For example we have two simple protocols:
protocol Container {
var items: [Item] {get}
}
protocol Item {
var name: String {get}
}
And we want to use a function that requires Item to be Equatable, like:
extension Container {
func indexOfItem(_ item: Item) -> Int? {
items.firstIndex(of: item)
}
}
Yet we can't write it like this, as firstIndex requires argument conforming to Equatable. But Item being protocol, can't conform to Equatable.
I've tried to implement it using type erasure, but it became too complicated.
So I am curious is there some workaround to implement such functions (provided that actual items are equatable)?

Since you're using func firstIndex of an array in this func indexOfItem(_ item: Item) -> Int? therefore the Item has to be a concrete object (behind the scene of firstIndex func is comparing each element of an array and print out the index of the element).
There are 2 ways to do this
First is using associatedtype to keep your protocol generic
protocol Item: Equatable {
var name: String { get }
}
protocol Container {
associatedtype Item
var items: [Item] { get }
}
struct MyItem: Item {
var name: String
}
extension Container where Item == MyItem {
func indexOfItem(_ item: Item) -> Int? {
return items.firstIndex(of: item)
}
}
Second is using an equatable object MyItem instead a protocol Item inside the Container protocol
protocol Item {
var name: String { get }
}
protocol Container {
var items: [MyItem] { get }
}
struct MyItem: Item, Equatable {
var name: String
}
extension Container {
func findIndex(of item: MyItem) -> Int? {
return items.firstIndex(of: item)
}
}

Finally find simple enough solution:
То make protocol generic with associated type and constraint
this type to Equatable.
public protocol Container {
associatedtype EquatableItem: Item, Equatable
var items: [EquatableItem] {get}
}
public protocol Item {
var name: String {get}
}
public extension Container {
func indexOfItem(_ item: EquatableItem) -> Int? {
items.firstIndex(of: item)
}
}
This compiles and now if I have some types
struct SomeContainer {
var items: [SomeItem]
}
struct SomeItem: Item, Equatable {
var name: String
}
I only need to resolve associatedtype to provide protocol conformance for SomeContainer type:
extension SomeContainer: Container {
typealias EquatableItem = SomeItem
}

Related

How to express a relationship between generic parameters in a SwiftUI view

I have defined the following protocols:
protocol ListableArrayElement: Identifiable, Equatable {
associatedtype T = Hashable
var id:T { get }
}
protocol Listable: ObservableObject, RandomAccessCollection where Element == ArrayElement {
associatedtype ArrayElement: ListableArrayElement
var _elements: [ArrayElement] { get set }
var count: Int { get }
var isEmpty: Bool { get }
var endIndex: Int { get }
var startIndex: Int { get }
subscript(position: Int) -> ArrayElement { get set }
func index(after i: Int) -> Int
func append(_ element: ArrayElement)
func insert(_ newElement: ArrayElement, at i: Int)
func remove(at index: Int)
func removeAll()
func index(of element: ArrayElement) -> Int?
}
protocol FavouritesArray: Listable, Codable where Element: Codable {
// MARK: - PROPERTIES
var _elements: [Element] { get set }
var key: String { get set }
init()
init(key: String)
}
There is an associated extension to FavouritesArray that provides conforming types with the ability to add/remove elements, and persist/load the array to/from UserDefaults via the key. All well and good. (Also note the listable protocol helps me avoid writing some boiler plate code for ViewModels that have an array of 'something' at their heart.)
Now I also want to write a generic SwiftUI view that can build a menu, using the FavouriteArray functions. I am struggling to understand how to express the type signature:
I'm passing instances of types conforming to FavouritesArray via EnvironmentObject, and thus want to write something like:
struct FavouritesArrayView<Favourites: FavouritesArray, Favourite>: View
where Favourite == FavouritesArray.Element {
#EnvironmentObject var favourites: Favourites
#ObservedObject var favourite: Favourite
// Other properties here
var body: some View {
// Layout code here
}
}
This gives the compiler error: Associated type 'Element' can only be used with a concrete type or generic parameter base
Any tips on how to achieve this?
First of all you need to declare that Favourite conforms to ObservableObject and then the where condition should use your associated type and not the protocol it conforms to, where Favourites.Element == Favourite
struct FavouritesArrayView<Favourites: FavouritesArray, Favourite: ObservableObject>: View where Favourites.Element == Favourite

How can I erase generics from a type in Swift?

I need to declare an array like this:
var cups: [Cup<Drink>] = []
The Cup is a struct and the Drink is a protocol, but I got the following error:
Value of protocol type 'Drink' cannot conform to 'Drink'; only struct/enum/class types can conform to protocols
I know the protocol type 'Drink' can be erased by a AnyDrink struct, the flowing code is an example.
But in fact, the Type-Erasure will become extremely complicated when associatetype, Self and static-method (in the case of non-final class adopts Drink, such as the protocol Equatable with static method ==) are used in Drink.
Is there a better way to declare the cups array?
Or: Is there any easy way to make Type-Erasure? It should be a builtin feature.
protocol Drink {
...
}
struct AnyDrink: Drink {
let drink: Drink
...
}
struct Water: Drink {
...
}
struct Coffee: Drink {
...
}
struct Tea: Drink {
...
}
struct Cup<T: Drink> {
private(set) var drink: T?
mutating func bottomUp() {
drink = nil
}
}
struct Waiter {
var cups: [Cup<AnyDrink>] = []
mutating func makeACupOfSth(_ cup: Cup<AnyDrink>) {
cups.append(cup)
}
mutating func pleaseGiveMeACupOfSthToDrink() -> Cup<AnyDrink> {
return cups.removeFirst()
}
static func excuse(_ customer: Customer) -> Waiter {
return Waiter()
}
}
struct Customer {
var me: Self { self }
func drink() {
var waiter = Waiter.excuse(me)
var cup = waiter.pleaseGiveMeACupOfSthToDrink()
cup.bottomUp()
}
}
For question Is there a better way to declare the cups array?:
You can use another protocol called DrinkGeneric like this and implement it by Cup Struct:
protocol DrinkGeneric {
func sample()
func typOfDrink() -> Drink.Type
}
struct Cup<T: Drink>: DrinkGeneric {
public var drink: T?
mutating func bottomUp() {
drink = nil
}
public func typeOfDrink() -> Drink.Type {
return type(of: drink!)
}
func sample() {
print("sample")
}
}
Then create an array with type DrinkGeneric like this:
var cups: [DrinkGeneric] = [Cup(drink: Water()), Cup(drink: Tea())]
For check type:
if cups[0].typeOfDrink() is Water.Type {
// Any work
}

Swift Generics: How to avoid this strange behaviour?

I met with the strange behaviour. Maybe i'm doing it wrong but I have not a clue.
Error: Cannot assign value of type '[Itemable]' to type '[T]'
protocol Itemable {
var id: Int { get set }
}
protocol Paginatable {
var items: [Itemable] { get set }
}
class Fetcher<T: Itemable, P: Paginatable> {
var items = [T]()
func onReceive(pagination: P) {
items = pagination.items
}
}
Class Fetcher expects a T type, i.e. a specific type conforming to Itemable.
So, for example, if you had a type Foo: Itemable, Fetcher<Foo, ...> would expect to work with Foos - not any Itemable, like AnotherFoo: Itemable - but only Foo.
Yet Ps don't have such as restriction on their items property - both Foo and AnotherFoo and something else could be in items.
So, you're basically trying to do the following:
let items: [Itemable] = [...]
let foos: [Foo] = items // error
If you want to constrain P to hold the same items as T, then you could do the following:
protocol Itemable {
var id: Int { get set }
}
protocol Paginatable {
associatedtype Item: Itemable
var items: [Item] { get set }
}
class Fetcher<T, P: Paginatable> where P.Item == T {
var items = [T]()
func onReceive(pagination: P) {
items = pagination.items
}
}
You need Paginatable to be a generic. Then everything falls into place. You do not want it to declare an array of Itemable but an array of some Itemable adopter. That is exactly what a generic lets you say.
In other words:
protocol Paginatable {
var items: [Itemable] { get set }
}
...means: "Whoever adopts me (I'm looking at you, Fetcher), must declare that items is [Itemable]." That is not what you want Fetcher to have to say. But you are forcing Fetcher to say that.
Instead, say this:
protocol Paginatable {
associatedtype T : Itemable
var items: [T] { get set }
}
Now you are saying: "Whoever adopts me can declare items as an array of any type as long as that type adopts Itemable." That is exactly what you do want to say.
To illustrate further, this is illegal:
protocol Itemable { }
protocol Paginatable {
var items: [Itemable] { get set }
}
struct Dummy : Itemable {}
struct Fetcher : Paginatable { // error
var items: [Dummy]
}
But this is fine:
protocol Itemable { }
protocol Paginatable {
associatedtype T : Itemable
var items: [T] { get set }
}
struct Dummy : Itemable {}
struct Fetcher : Paginatable {
var items: [Dummy]
}
And that second thing is exactly what you want to say. Now that you've understood that, if you then want to make Fetcher a generic too, no problem:
protocol Itemable { }
protocol Paginatable {
associatedtype T : Itemable
var items: [T] { get set }
}
struct Dummy : Itemable {}
struct Fetcher <T:Itemable> : Paginatable {
var items: [T]
}

How to generate a binding for each array element

If I had a #State or an #ObservedObject variable with an array property, and I wanted to use List and pass a binding of each element of the array into some child View (e.g. Toggle or TextField), is there a standard way to do that?
Simplified example:
struct Person: Identifiable {
var id: UUID = .init()
var name: String
var isFavorite: Bool = false
}
struct ContentView: View {
#State var people = [Person(name: "Joey"), Person(name: "Chandler")]
var body: some View {
List(people) { person in
HStack() {
Text(person.name)
Spacer
Toggle("", isOn: $person.isFavorite) // <- this obviously doesn't work
}
}
}
}
This seems like a fairly common scenario, but I can't figure out an obvious solution aside from manually building a separate array of bindings.
The only elegant solution I came up with (I'll add it as an answer, if there isn't something better) was to create an extension of Binding of a RandomAccessCollection to itself conform to a RandomAccessCollection, which has bindings as elements, like so:
extension Binding: RandomAccessCollection
where Value: RandomAccessCollection & MutableCollection {
// more code here
}
// more required extensions to Collection and Sequence here
UPDATE
In iOS13 release notes (deprecation section), SwiftUI dropped the conformance of Binding to Collection, and instead offered a workaround, so I'm updating this answer with their suggestion.
The idea is to extend RandomAccessCollection to add a .index() method, which works similarly to .enumerated() by creating a collection of tuples of index and element, but unlike .enumerated() conforms to a RandomAccessCollection, which List and ForEach require.
The usage is:
List(people.indexed(), id: \.1.id) { (i, person) in
HStack() {
Toggle(person.name, isOn: $people[i].isFavorite)
}
And the implementation of .indexed() is:
struct IndexedCollection<Base: RandomAccessCollection>: RandomAccessCollection {
typealias Index = Base.Index
typealias Element = (index: Index, element: Base.Element)
let base: Base
var startIndex: Index { base.startIndex }
var endIndex: Index { base.startIndex }
func index(after i: Index) -> Index {
base.index(after: i)
}
func index(before i: Index) -> Index {
base.index(before: i)
}
func index(_ i: Index, offsetBy distance: Int) -> Index {
base.index(i, offsetBy: distance)
}
subscript(position: Index) -> Element {
(index: position, element: base[position])
}
}
extension RandomAccessCollection {
func indexed() -> IndexedCollection<Self> {
IndexedCollection(base: self)
}
}
ORIGINAL
Here's what I wanted to achieve:
List($people) { personBinding in
HStack() {
Text(personBinding.wrappedValue.name)
Spacer()
Toggle("", isOn: personBinding.isFavorite)
}
}
In other words, pass the binding of an array, and get a binding of an element in List's closure.
To achieve that, I created an extension of Binding that makes a Binding of any RandomAccessCollection into a RandomAccessCollection of bindings:
// For all Bindings whose Value is a collection
extension Binding: RandomAccessCollection
where Value: RandomAccessCollection & MutableCollection {
// The Element of this collection is Binding of underlying Value.Element
public typealias Element = Binding<Value.Element>
public typealias Index = Value.Index
public typealias SubSequence = Self
public typealias Indices = Value.Indices
// return a binding to the underlying collection element
public subscript(position: Index) -> Element {
get {
.init(get: { self.wrappedValue[position] },
set: { self.wrappedValue[position] = $0 })
}
}
// other protocol conformance requirements routed to underlying collection ...
public func index(before i: Index) -> Index {
self.wrappedValue.index(before: i)
}
public func index(after i: Index) -> Index {
self.wrappedValue.index(after: i)
}
public var startIndex: Index {
self.wrappedValue.startIndex
}
public var endIndex: Index {
self.wrappedValue.endIndex
}
}
This also requires explicit conformance to inherited protocols:
extension Binding: Sequence
where Value: RandomAccessCollection & MutableCollection {
public func makeIterator() -> IndexingIterator<Self> {
IndexingIterator(_elements: self)
}
}
extension Binding: Collection
where Value: RandomAccessCollection & MutableCollection {
public var indices: Value.Indices {
self.wrappedValue.indices
}
}
extension Binding: BidirectionalCollection
where Value: RandomAccessCollection & MutableCollection {
}
And, if the underlying value is an Identifiable, then it makes the Binding conform to Identifiable too, which removes the need to use id::
extension Binding: Identifiable where Value: Identifiable {
public var id: Value.ID {
self.wrappedValue.id
}
}

Conformance to a protocol (as a protocol)

Say I have a protocol Item, and a struct ConcreteItem that conforms to it.
protocol Item {
var name: String { get }
}
struct ConcreteItem: Item {
let name: String
}
At some point I want to have two sets of ConcreteItem.
let set1 = Set([ConcreteItem(name: "item1")])
let set2 = Set([ConcreteItem(name: "item2"), ConcreteItem(name: "item1")])
Which I'd expect to return the item with name "item1".
I can make ConcreteItem conform to Hashable and the Set code will work. However, lets say I also had the following:
struct AnotherConcreteItem: Item {
let name: String
}
I'd like AnotherConcreteItem to also conform to Hashable simply for having conformed to Item.
However, when I try to implement that idea:
extension Item: Hashable {
var hashValue: Int {
return name.characters.count
}
}
I get the following error: Extension of protocol 'Item' cannot have an inheritance clause.
Extension of protocol 'Item' cannot have an inheritance clause
Here item is the protocol so conforming Hashable protocol will not work. For more details Refer here
What you are trying to do is possible with some protocols, but not all. If the protocol you are trying to conform to does not have any associated types or Self, this is possible.
Example:
protocol A {
func foo()
}
protocol Item: A {
var name: String { get }
}
struct ConcreteItem: Item {
let name: String
}
extension Item {
func foo() {
}
}
Everything that conforms to Item will also conform to A.
However, Hashable has Self constraints. To conform to Hashable, you must also conform to Equatable. To conform to Equatable, the implementation of == must be in a concrete class, not another protocol because Equatable can't be used as a parameter type. The most you can do is something like this:
protocol Item: Hashable {
var name: String { get }
}
struct ConcreteItem: Item {
let name: String
// you need to implement == in every concrete class
static func ==(lhs: ConcreteItem, rhs: ConcreteItem) -> Bool {
return ...
}
}
extension Item {
var hashValue: Int {
return name.characters.count
}
}