update: add same error about Hashable
I have created an Identifiable compliant protocol and compliant structures. Then, when I create the list and reference it in ForEach, I get the error Type 'any TestProtocol' cannot conform to 'Identifiable'(I get the same error about Hashable).
How should I fix this program?
If I write ForEach(list, id: \.id) , it works, but I don't think it makes sense to be Identifiable compliant.
import SwiftUI
protocol TestProtocol: Identifiable, Hashable {
var id: UUID { get set }
var name: String { get set }
func greeting() -> String
static func == (lhs: Self, rhs: Self) -> Bool
}
extension TestProtocol {
static func == (lhs: Self, rhs: Self) -> Bool {
return lhs.id == rhs.id
}
}
struct Person: TestProtocol {
var id = UUID()
var name: String
func greeting() -> String {
return "my name is \(name) and I'm a human."
}
}
struct Dog: TestProtocol {
var id = UUID()
var name: String
func greeting() -> String {
return "my name is \(name) and I'm a dog."
}
}
struct ContentView: View {
var list: [any TestProtocol] = [Person(name: "p1"), Dog(name: "d1")]
#State var selected: any TestProtocol
var body: some View {
VStack {
Picker(selection: $selected) { // Type 'any TestProtocol' cannot conform to 'Hashable'
ForEach(list) { l in // Type 'any TestProtocol' cannot conform to 'Identifiable'
Text(l.greeting()).tag(l) // Type 'any TestProtocol' cannot conform to 'Hashable'
}
} label: {
Text("select")
}
}
}
}
Your error message complaining about Hashable is a "red hering". The protocol TestProtocol, and therefor all structs conforming to it, conforms to Hashable.
let person = Person(name: "IAmHashable")
print(person.hashValue)
The reason this is failing is within the Picker. It needs a concrete type and not a protocol. One solution would be to create a "Container" type and a custom binding that handles this.
struct Container: Identifiable, Hashable{
//implement the same equality as in your TestProtocol
static func == (lhs: Container, rhs: Container) -> Bool {
rhs.wrapped.id == lhs.wrapped.id
}
func hash(into hasher: inout Hasher) {
hasher.combine(wrapped)
}
var wrapped: any TestProtocol
//for convenience
var id: UUID {wrapped.id}
}
and the ContentView:
struct ContentView: View {
let startArr: [any TestProtocol] = [Person(name: "p1"), Dog(name: "d1")]
#State private var selected: (any TestProtocol)?
var body: some View {
// list of wrapped protocols
var list: [Container] = { startArr.map{Container(wrapped: $0)}}()
// binding
let selectionBinding: Binding<Container> = .init {
let returninstance = list.first { cont in
cont.id == selected?.id
}
return returninstance ?? list[0]
} set: { container in
selected = container.wrapped
}
// viewCode
VStack {
Picker(selection: selectionBinding) {
ForEach(list) { l in
Text(l.wrapped.greeting())
.tag(l)
}
} label: {
Text("select")
}
// confirmation selection changed
Text(selected?.name ?? "no Selection")
}
}
}
Remarks:
This solution has a few drawbacks:
your initial array startArr should never be empty, else return returninstance ?? list[0] will break you code. This can be handled, but I think this is out of the scope of this question.
the equality comparison of Container needs to be the same as in TestProtocol as you cannot compare two any Protocols
on the appearance of ContainerView selected will be nil until something is selected. Any usage, e.g.: the Text element in this case, needs to deal with this. But you could probably set this in .onApear.
Related
I'm trying to extend a protocol so that a certain few impls of the protocol have a view associated with them. However, because a SwiftUI View is a protocol, this is proving to be a challenge.
import SwiftUI
protocol ParentProtocol {
var anyProperty: String { get }
}
protocol ChildProtocol : ParentProtocol {
associatedtype V
var someView: V { get }
}
class ChildImpl : ChildProtocol {
var someView : some View {
Text("Hello World")
}
var anyProperty: String = ""
}
class ChildMgr {
var child: ParentProtocol = ChildImpl()
func getView() -> some View {
guard let child = child as? ChildProtocol else { return EmptyView() }
return child.someView
}
}
Its not clear to me where to constrain the ChildProtocol's associated type to a View (or Text for that matter).
At the guard let child = ... I get the following compiler error:
Protocol 'ChildProtocol' can only be used as a generic constraint because it has Self or associated type requirements
and when returning the chid's view I get:
Member 'someView' cannot be used on value of protocol type 'ChildProtocol'; use a generic constraint instead
I think the answer may be in this thread: https://developer.apple.com/forums/thread/7350
but frankly its confusing on how to apply it to this situation.
Don't use runtime checks. Use constrained extensions.
I also don't see a reason for you to be using classes.
protocol ChildProtocol: ParentProtocol {
associatedtype View: SwiftUI.View
var someView: View { get }
}
final class ChildImpl: ChildProtocol {
var someView: some View {
Text("Hello World")
}
var anyProperty: String = ""
}
final class ChildMgr<Child: ParentProtocol> {
var child: Child
init(child: Child) {
self.child = child
}
}
extension ChildMgr where Child: ChildProtocol {
func getView() -> some View {
child.someView
}
}
extension ChildMgr {
func getView() -> some View {
EmptyView()
}
}
extension ChildMgr where Child == ChildImpl {
convenience init() {
self.init(child: .init())
}
}
Imagine a card board. All cards are on a view I call CardBoard.
All cards are on an array:
var cards:[Card]
Every card has its own position.
This is Card
struct Card: View {
let id = UUID()
var name:String
var code:String
var filename:String
var position = CGPoint(x: 200, y: 250)
init(name:String, code:String, filename:String) {
self.name = name
self.code = code
self.filename = filename
}
var body: some View {
Image(filename)
.position(position)
}
}
Now I want to draw them on the screen.
var body: some View {
ZStack {
ForEach(cards, id:\.self) {card in
}
}
}
when I try this, Xcode tells me
Generic struct 'ForEach' requires that 'Card' conform to 'Hashable'
when I add Hashable to Card
struct Card: View, Hashable {
1. Stored property type 'CGPoint' does not conform to protocol 'Hashable', preventing synthesized conformance of 'Card' to 'Hashable'
any ideas?
THANKS EVERYBODY, but because is not good to post a link as a solution, I am posting here:
The solution is to make the view Hashable and add this:
extension CGPoint : Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(x)
hasher.combine(y)
}
}
There's a bunch of types that need the same treatment (which are often named 2-tuples). e.g.
extension CGPoint: HashableSynthesizable { }
extension CLLocationCoordinate2D: HashableSynthesizable { }
extension MKCoordinateRegion: HashableSynthesizable { }
extension MKCoordinateSpan: HashableSynthesizable { }
/// A type whose `Hashable` conformance could be auto-synthesized,
/// but either the API provider forgot, or more likely,
/// the API is written in Objective-C, and hasn't been modernized.
public protocol HashableSynthesizable: Hashable { }
public extension HashableSynthesizable {
static func == (hashable0: Self, hashable1: Self) -> Bool {
zip(hashable0.hashables, hashable1.hashables).allSatisfy(==)
}
func hash(into hasher: inout Hasher) {
hashables.forEach { hasher.combine($0) }
}
}
private extension HashableSynthesizable {
var hashables: [AnyHashable] {
Mirror(reflecting: self).children
.compactMap { $0.value as? AnyHashable }
}
}
The issue is that your Card's point cannot be automatically synthesized because it's a CGPoint. Have you tried extending CGPoint to conform to Hashable?
Here's a really helpful discussion about creating the hashing logic for CGPoint: https://codereview.stackexchange.com/questions/148763/extending-cgpoint-to-conform-to-hashable
I want make a Generic PreferenceKey, which I got 2 issues with my codes.
First I need define () for T, for using normal type like String, Int as String() or Int() so I need memberwise initializer for T.
Second Xcode complain that my PreferenceKey does not conform to Equatable, while I did it! <T: Equatable>
How can I solve this 2 issues? thanks
struct ContentView: View {
#State private var stringOfText: String = "Hello, world!"
var body: some View {
Text(stringOfText)
.preference(key: CustomPreferenceKey.self, value: stringOfText)
.onPreferenceChange(CustomPreferenceKey.self) { newValue in print(newValue) }
}
}
struct CustomPreferenceKey<T: Equatable>: PreferenceKey {
static var defaultValue: T { get { return T() } }
static func reduce(value: inout T, nextValue: () -> T) { value = nextValue() }
}
Here is a possible approach to move with. Tested as worked with Xcode 12.4 / iOS 14.4
protocol Initable {
init()
}
extension String: Initable {
}
struct CustomPreferenceKey<T: Equatable & Initable>: PreferenceKey {
typealias Value = T
static var defaultValue: T { get { T() } }
static func reduce(value: inout Value, nextValue: () -> Value) {
value = nextValue()
}
}
struct ContentView: View {
#State private var stringOfText: String = "Hello, world!"
var body: some View {
Text(stringOfText)
.preference(key: CustomPreferenceKey<String>.self, value: stringOfText)
.onPreferenceChange(CustomPreferenceKey<String>.self) { newValue in print(newValue) }
}
}
I want to create a Swift protocol that my different enums can conform to, so I can use the same 'type' while utilizing the enum's rawValue. Basically, the protocol should look like this:
protocol SidebarCustomFilter {
var image: UIImage { get }
var filterPredicate: NSPredicate { get }
}
An example enum that conforms to this:
enum SidebarFilterLogs : String, CaseIterable, SidebarCustomFilter{
case filterAll = "All"
case filterThisWeek = "This Week"
var image: UIImage {
return UIImage(systemName: "tray.full.fill")!
}
var filterPredicate: NSPredicate {
return NSPredicate.init(format: "TRUEPREDICATE")
}
}
Now I want to use this enum inside a struct:
struct CJSidebarFilterItem: Hashable {
private var identifier: String = ""
var sectionType: SectionType
var sidebarCustomFilter: SidebarCustomFilter?
init(logsFilter: SidebarFilterLogs) {
self.sectionType = .filters
self.sidebarCustomFilter = logsFilter
self.identifier = UUID().uuidString
}
func hash(into hasher: inout Hasher) {
hasher.combine(identifier)
}
static func == (lhs: CJSidebarFilterItem, rhs: CJSidebarFilterItem) -> Bool {
return lhs.identifier == rhs.identifier
}
}
So far so good. However, if I try to use the 'rawValue' for the enum (of the protocol type I've described above), it gives me an error
sidebarItem.sidebarCustomFilter?.rawValue // Value of type 'SidebarCustomFilter' has no member 'rawValue'
That makes sense, since the SidebarCustomFilter is only a protocol. However, if I try to make it inherit from RawRepresentable (which should allow it to work with rawValue),
protocol SidebarCustomFilter: RawRepresentable {
var image: UIImage { get }
var filterPredicate: NSPredicate { get }
}
I get a different error:
var sidebarCustomFilter: SidebarCustomFilter? // Protocol 'SidebarCustomFilter' can only be used as a generic constraint because it has Self or associated type requirements
I believer this has something to do with RawRepresentable using an associatedtype RawValue, but I'm not sure how to resolve it.
So how do I get my protocol to work such that it enforces that only other enums are conforming to it (and hence it's valid to use 'rawValue' against it)?
Found a solution:
protocol SidebarCustomFilter {
var name: String { get }
var image: UIImage { get }
var filterPredicate: NSPredicate { get }
}
extension SidebarCustomFilter where Self: RawRepresentable {
var name: Self.RawValue {
return self.rawValue
}
}
Now I can use sidebarItem.sidebarCustomFilter?.name instead of directly asking for the rawValue, and it works!
EDIT: adding code to show how it's stored:
struct CJSidebarFilterItem: Hashable {
private var identifier: String = ""
var sectionType: SectionType
var sidebarCustomFilter: SidebarCustomFilter? // storing as generic type
init(logsFilter: SidebarFilterLogs) {
self.sectionType = .filters
self.sidebarCustomFilter = logsFilter
self.identifier = UUID().uuidString
}
func hash(into hasher: inout Hasher) {
hasher.combine(identifier)
}
static func == (lhs: CJSidebarFilterItem, rhs: CJSidebarFilterItem) -> Bool {
return lhs.identifier == rhs.identifier
}
}
and how it's used:
let titleString = sidebarItem.sidebarCustomFilter?.name ?? ""
Node is a generic type.
struct Node<T: Hashable>: Hashable {
var label: T
init(_ label: T) {
self.label = label
}
var hashValue : Int {
get {
return label.hashValue
}
}
}
extension Node : Equatable {}
// MARK: Equatable
func ==<T>(lhs: Node<T>, rhs: Node<T>) -> Bool {
return lhs.label == rhs.label
}
But it doesn't work when I try the following:
let nodes = Set<Node<String>>()
The compiler complains that Node<String> doesn't conform to Hashable. How to make Node<String> conform to Hashable?
You have to implement the == method as part of the Equatable protocol for your struct as well:
func ==<T, K>(lhs:Node<T>, rhs:Node<K>) -> Bool {
return lhs.hashValue == rhs.hashValue
}
The reason for that is Hashable inherits from Equatable.
The following is a complete working playground example:
struct Node<T: Hashable> : Hashable {
var label: T
init(_ label: T) {
self.label = label
}
var hashValue : Int {
get {
return label.hashValue
}
}
}
func ==<T>(lhs:Node<T>, rhs:Node<T>) -> Bool {
return lhs.hashValue == rhs.hashValue
}
var nodes = Set<Node<String>>()
nodes.insert(Node("hi"))
nodes.insert(Node("ho"))
nodes.insert(Node("hi"))