"Type 'User' does not conform to protocol 'Equatable'/ 'Hashable' - struct

I want to add a variable of type View to my struct: User and then later add individual views to my users (as shown in Manuelle). However I get the error "Protocol 'View' can only be used as a generic constraint because it has Self or associated type requirements" and "Type 'User' does not conform to protocol 'Equatable'/ Hashable".
struct User : Hashable {
let name: String
let age: Int
let profilePicture, status, icon: String
let view: View
}
struct MyUserView: View {
let users = [
User(name: "Manuelle", age: 23, profilePicture: "person", status: "inactive", icon: "message.fill", view: UserProfileView()),
User(name: "Michael", age: 39, profilePicture: "person", status: "active 12 minutes ago", icon: "square.on.square")
]
var body: some View {
NavigationView {
List {
ForEach(users, id: \.self) { user in
HStack {
Text(user.name)
Image(systemName: user.profilePicture)
}
}
}
}
}
}

Remove let view: View from the User struct, that's the main problem.
Also, you can't supply id: \.self to the ForEach view. id has to be a property that is a unique identifier of the struct it cannot be the struct itself because you'll get a crash when the array of structs changes. You have a few options to fix it:
ForEach(users, id: \.name) { user in
Or better:
struct User : Identifiable {
var id: String {
return name
}
Because then you can simply do:
ForEach(users) { user in
But normally we do this:
struct User : Identifiable {
let id = UUID()
Unless of course the userID comes from a server.

Related

Widget unable to display Core Data properties

I am unable to feed and read core data objects/properties into my Widget view. I am only able to feed and present it Doubles/Strings beforehand and it will work.
Prior to this, I have made sure all Core Data related files are shared between both the Widget Extension and the main app. The App Group has been set up and the data can be loaded (I am able to load all transactions in the Provider, calculate the total, and feed that double into the Widget view).
However, when it comes to actually feeding an array of transactions into the Widget View, the data cannot be loaded and all I see are empty rectangles. However, I'm pretty certain the data is accessible as the colours are accurate, and if I display the count of the array, it is accurate too. Below is my attached code.
struct RecentExpenditureWidget: Widget {
let kind: String = "ExpenditureWidget"
var body: some WidgetConfiguration {
IntentConfiguration(kind: kind, intent: RecentWidgetConfigurationIntent.self, provider: Provider()) { entry in
ExpenditureWidgetEntryView(entry: entry)
}
.configurationDisplayName("Recent Transactions")
.description("View your latest expenses.")
.supportedFamilies([.systemSmall])
}
}
struct Provider: IntentTimelineProvider {
typealias Intent = RecentWidgetConfigurationIntent
public typealias Entry = RecentWidgetEntry
func placeholder(in context: Context) -> RecentWidgetEntry {
RecentWidgetEntry(date: Date(), amount: loadAmount(type: .week), transactions: loadTransactions(type: .week, count: 3))
}
func getSnapshot(for configuration: RecentWidgetConfigurationIntent, in context: Context, completion: #escaping (RecentWidgetEntry) -> ()) {
let entry = RecentWidgetEntry(date: Date(), amount: loadAmount(type: configuration.duration), transactions: loadTransactions(type: configuration.duration, count: 3))
completion(entry)
}
func getTimeline(for configuration: RecentWidgetConfigurationIntent, in context: Context, completion: #escaping (Timeline<Entry>) -> ()) {
let entry = RecentWidgetEntry(date: Date(), amount: loadAmount(type: configuration.duration), transactions: loadTransactions(type: configuration.duration, count: 3))
let timeline = Timeline(entries: [entry], policy: .atEnd)
completion(timeline)
}
func loadAmount(type: TimePeriod) -> Double {
let dataController = DataController()
let itemRequest = dataController.fetchRequestForRecentTransactions(type: type)
let transactions = dataController.results(for: itemRequest)
var holdingTotal = 0.0
transactions.forEach { transaction in
holdingTotal += transaction.wrappedAmount
}
return holdingTotal
}
func loadTransactions(type: TimePeriod, count: Int) -> [Transaction] {
let dataController = DataController()
let itemRequest = dataController.fetchRequestForRecentTransactionsWithCount(type: type, count: count)
return dataController.results(for: itemRequest)
}
}
struct RecentWidgetEntry: TimelineEntry {
let date: Date
let amount: Double
let transactions: [Transaction]
}
struct ExpenditureWidgetEntryView : View {
let entry: Provider.Entry
var body: some View {
VStack(spacing: 5) {
ForEach(entry.transactions) { transaction in
HStack(spacing: 2) {
Capsule()
.fill(Color(transaction.category?.wrappedColour ?? ""))
.frame(width: 4)
Text(transaction.wrappedNote)
.lineLimit(1)
.font(.system(size: 12, weight: .regular, design: .rounded))
.foregroundColor(Color.PrimaryText)
Text(transaction.amount, format: .currency(code: Locale.current.currencyCode ?? "USD"))
.font(.system(size: 12, weight: .regular, design: .rounded))
.foregroundColor(Color.SubtitleText)
}
.frame(height: 15)
}
}
.padding(15)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.PrimaryBackground)
}
}
This is the result I get on my actual devices:
The only (by extremely dumb) workaround I have found is to extract each property of a transaction in the loadTransaction() function, and then feed it into the Widget.
func loadTransactions(type: TimePeriod, count: Int) -> String {
let dataController = DataController()
let itemRequest = dataController.fetchRequestForRecentTransactionsWithCount(type: type, count: count)
return dataController.results(for: itemRequest).first!.wrappedNote
}

Swift UI, removing item from array, while looping in it throws Fatal Error: Index out of range [duplicate]

I am trying to remove rows inside a ForEach. Removing the last row always throws an index out of range exception. Removing any other row does not.
ForEach(Array(player.scores.enumerated()), id: \.element) { index, score in
HStack {
if self.isEditSelected {
Button(action: {
self.player.scores.remove(at: index)
}, label: {
Image("delete")
})
}
TextField("\(score)", value: self.$player.scores[index], formatter: NumberFormatter())
}
}
I have tried using ForEach(player.indices...) & ForEach(player.scores...), but see the same problem.
Looks to me like the crash happens here self.$player.scores[index], as hardcoding the index to any value other that the last row is working.
Does anyone know how to fix this? Or if there is a better approach.
Here is fix
ForEach(Array(player.scores.enumerated()), id: \.element) { index, score in
HStack {
if self.isEditSelected {
Button(action: {
self.player.scores.remove(at: index)
}, label: {
Image("delete")
})
}
TextField("\(score)", value: Binding( // << use proxy binding !!
get: { self.player.scores[index] },
set: { self.player.scores[index] = $0 }),
formatter: NumberFormatter())
}
}
Based on #Asperi answer
public extension Binding where Value: Equatable {
static func proxy(_ source: Binding<Value>) -> Binding<Value> {
self.init(
get: { source.wrappedValue },
set: { source.wrappedValue = $0 }
)
}
}
You can use this as follows:
TextField("Name", text: .proxy($variable))
Xcode 13.0 beta introduced a new way to establish two-way-bindings between the elements of a collection and the views built by ForEach / List.
This method fixes the crash related to deleting the last row.
struct Score: Identifiable {
let id = UUID()
var value: Int
}
struct Player {
var scores: [Score] = (1...10).map {_ in .init(value: Int.random(in: 0...25))}
}
struct BindingTest: View {
#State private var player = Player()
var body: some View {
List {
ForEach($player.scores) { $score in
HStack {
TextField("\(score.value)", value: $score.value,
formatter: NumberFormatter())
}
}
.onDelete { player.scores.remove(atOffsets: $0)}
}
}
}

Cannot convert value of type 'NSSet?' to expected argument type 'Range<Int>' (using CoreData)

Using CoreData I don't know how to solve that problem:
Error: Cannot convert value of type 'NSSet?' to expected argument type 'Range<Int>'
private func displayTopics(subject: Subject) -> some View {
NavigationView {
Form {
List {
ForEach(subject.topics) { topic in
NavigationLink(
destination: Text("Destination"),
label: {
Text("Navigate")
})
}
}
}
}
What should I do?
ForEach does not understand NSSet, you have to convert it in array:
if subject.topics != nil {
ForEach(Array(subject.topics! as Set), id: \.self) { topic in
NavigationLink(
destination: Text("Destination"),
label: {
Text("Navigate")
})
}
}

Swift protocol with properties that are not always used

This is a follow-up to this question: Can I have a Swift protocol without functions
Suppose I want to add more properties to my protocol:
protocol Nameable {
var name: String {get}
var fullName: String: {get}
var nickName: String: {get}
}
However, not every struct that conforms to this protocol may have a fullName and/or nickName. How do I go about this? Can I make these two properties somehow optional? Or maybe I need three separate protocols? Or do I just add them to each struct, but leave them empty, like this:
struct Person : Nameable {
let name: String
let fullName: String
let nickName: String
let age: Int
// other properties of a Person
}
let person = Person(name: "Steve", fullName: "", nickName: "Stevie", age: 21)
That compiles and works, but I don't know if this is the 'correct' approach?
Unlike in Objective-C, you cannot define optional protocol requirements in pure Swift. Types that conform to protocols must adopt all the requirements specified.
One potential way of allowing for optional property requirements is defining them as optionals, with a default implementation of a computed property that just returns nil.
protocol Nameable {
var name : String? { get }
var fullName : String? { get }
var nickname : String? { get }
}
extension Nameable {
var name : String? { return nil }
var fullName : String? { return nil }
var nickname : String? { return nil }
}
struct Person : Nameable {
// Person now has the option not to implement the protocol requirements,
// as they have default implementations that return nil
// What's cool is you can implement the optional typed property requirements with
// non-optional properties – as this doesn't break the contract with the protocol.
var name : String
}
let p = Person(name: "Allan")
print(p.name) // Allan
However the downside to this approach is that you potentially pollute conforming types with properties that they don't implement (fullName & nickName in this case).
Therefore if it makes no logical sense for a type to have these properties (say you wanted to conform City to Nameable – but cities don't (really) have nicknames), you shouldn't be conforming it to Nameable.
A much more flexible solution, as you say, would be to define multiple protocols in order to define these requirements. That way, types can choose exactly what requirements they want to implement.
protocol Nameable {
var name : String { get }
}
protocol FullNameable {
var fullName : String { get }
}
protocol NickNameable {
// Even types that conform to NickNameable may have instances without nicknames.
var nickname : String? { get }
}
// A City only needs a name, not a fullname or nickname
struct City : Nameable {
var name : String
}
let london = City(name: "London")
// Person can have a name, full-name or nickname
struct Person : Nameable, FullNameable, NickNameable {
var name : String
var fullName: String
var nickname: String?
}
let allan = Person(name: "Allan", fullName: "Allan Doe", nickname: "Al")
You could even use protocol composition in order to define a typealias to represent all three of these protocols for convenience, for example:
typealias CombinedNameable = Nameable & FullNameable & NickNameable
struct Person : CombinedNameable {
var name : String
var fullName: String
var nickname: String?
}
You can give a default implementation to these property using protocol extension and override the property in classes/structs where actually you needed
extension Nameable{
var fullName: String{
return "NoFullName"
}
var nickName: String{
return "NoNickName"
}
}
struct Foo : Nameable{
var name: String
}

Initialize nested struct definition

How do you initialize the following struct?
type Sender struct {
BankCode string
Name string
Contact struct {
Name string
Phone string
}
}
I tried:
s := &Sender{
BankCode: "BC",
Name: "NAME",
Contact {
Name: "NAME",
Phone: "PHONE",
},
}
Didn't work:
mixture of field:value and value initializers
undefined: Contact
I tried:
s := &Sender{
BankCode: "BC",
Name: "NAME",
Contact: Contact {
Name: "NAME",
Phone: "PHONE",
},
}
Didn't work:
undefined: Contact
Your Contact is a field with anonymous struct type. As such, you have to repeat the type definition:
s := &Sender{
BankCode: "BC",
Name: "NAME",
Contact: struct {
Name string
Phone string
}{
Name: "NAME",
Phone: "PHONE",
},
}
But in most cases it's better to define a separate type as rob74 proposed.
How about defining the two structs separately and then embedding "Contact" in "Sender"?
type Sender struct {
BankCode string
Name string
Contact
}
type Contact struct {
Name string
Phone string
}
if you do it this way, your second initialization attempt would work. Additionally, you could use "Contact" on its own.
If you really want to use the nested struct, you can use Ainar-G's answer, but this version isn't pretty (and it gets even uglier if the structs are deeply nested, as shown here), so I wouldn't do that if it can be avoided.
type NameType struct {
First string
Last string
}
type UserType struct {
NameType
Username string
}
user := UserType{NameType{"Eduardo", "Nunes"}, "esnunes"}
// or
user := UserType{
NameType: NameType{
First: "Eduardo",
Last: "Nunes",
},
Username: "esnunes",
}

Resources