I am getting the following error when I try to compare values in the task modifier.
Operator function '==' requires that 'Binding' conform to 'Equatable'
How can I make Binding confirm to Equatable?
The non-binding ordinary NotificationDetail already confirms to Equatable.
#ObservedObject var notificationsViewModel = NotificationsViewModel.shared
//NotificationsViewModel does a API call and puts the fetched data in the Notifications Model
var body: some View {
VStack {
ForEach($notificationsViewModel.notifications.notificationsDetails) { $notificationsDetail in
VStack(alignment: .leading) {
if notificationsDetail.notificationsCategoriesId == 2 {
NotificationsFriendRequestCell(notificationsDetail: $notificationsDetail, viewModel: viewModel, notificationsViewModel: notificationsViewModel, tabSelection: $tabSelection) //ReceiveFriendRequestCell(user: user, viewModel: viewModel)
}
else if notificationsDetail.notificationsCategoriesId == 3 {
NotificationsFriendRequestApprovedCell(notificationsDetail: $notificationsDetail, viewModel: viewModel, notificationsViewModel: notificationsViewModel, tabSelection: $tabSelection)
}
}
.task { //task modifier for infinite scrolling
//BELOW LINE GETTING ERRROS
if $notificationsDetail == $notificationsViewModel.notifications.notificationsDetails.last {
print("task modifier last item")
numberOfSkips += 10
notificationsViewModel.getMyNotifications(isOnlyUnread: false, skip: numberOfSkips, limit: 10)
}
}
} //ForEach end
} //VStack end
Model
struct Notifications: Codable, Identifiable {
let id = UUID()
let numberOfNotifications: Int
var notificationsDetails: [NotificationsDetail]
enum CodingKeys: String, CodingKey {
case numberOfNotifications = "number_of_notifications"
case notificationsDetails = "notifications"
}
}
struct NotificationsDetail: Codable, Identifiable, Equatable {
let id: Int
let notificationsCategoriesId: Int
let questionsUsersName: String?
enum CodingKeys: String, CodingKey {
case id = "notifications_id"
case notificationsCategoriesId = "notifications_categories_id"
case questionsUsersName = "questions_users_name"
}
static var example = NotificationsDetail(id: 0, notificationsCategoriesId: 1, questionsId: 1, questionsUsersName: "")
init(id: Int, notificationsCategoriesId: Int, questionsUsersName: String?) {
self.id = id
self.notificationsCategoriesId = notificationsCategoriesId
self.questionsUsersName = questionsUsersName
}
}
ViewModel
class NotificationsViewModel: ObservableObject {
static let shared = NotificationsViewModel()
#Published var notifications: Notifications = Notifications.example
init(){
A()
}
func A () {
//do API calls and put data in #Published var notifications
}
I tried tweaking the values by removing or adding $ mark but get other errors...
if notificationsDetail == notificationsViewModel.notifications.notificationsDetails.last
//Error: Reference to captured var 'notificationsDetail' in concurrently-executing code
if $notificationsDetail == notificationsViewModel.notifications.notificationsDetails.last
//Error: Binary operator '==' cannot be applied to operands of type 'Binding<[NotificationsDetail]>.Element' (aka 'Binding<NotificationsDetail>') and '((NotificationsDetail) throws -> Bool) throws -> NotificationsDetail?'
Any advice would be highly appreciated!
Thanks in advance.
The reason for the error is you are handing your closure a binding through your ForEach, but you want to compare to the value of this binding. Accessing it via $ and .wrappedValue should work here. So change your code to:
if $notificationsDetail.wrappedValue == notificationsViewModel.notifications.notificationsDetails.last
Related
The line VStack shows error "Instance method 'sheet(item:onDismiss:content:)' requires that 'Int' conform to 'Identifiable'" but i think i dont make any mistake there? did i?
It feel like error come from my sheet complaining my Int should conform to 'Identifiable' however is not Int is an 'Identifiable' since it's primitive? how would i solve this problem?
struct SheetView: View {
#State var bindingItemDiscounts: [Double] =
[ 0.99, 0.25, 0.31, 11.99, 19.99, 0.1, 0.5]
#State var discountMoreThanTen: Int?
var body: some View {
VStack(alignment: .leading) { <- this line
Button {
discountMoreThanTen = bindingItemDiscounts.count
} label: { Text("Check Discounts") }
}.sheet(item: $discountMoreThanTen) { newValue in
if discountMoreThanTen ?? 0 <= 10 {
List(bindingItemDiscounts, id: \.self) { discount in
Text("Discount : \(discount)")
}
}
}
}
}.
There is no such concept as Primitive in Swift. The "Primitives" you refer to are normal structs.
Int does not conform to Identifiable.
To fix your problem just extend Int to conform to Identifiable like so:
extension Int: Identifiable {
public var id: Int { return self }
}
You are using the Binding<Item?> sheet. The Item is a type, so it must conform to Identifiable in order to be identified uniquely.
Also, Int is not a primitive in Swift. Int is basically a Type just like your other custom data types, so you need to make it Identifiable manually.
Add this extension in your file will fix this problem:
extension Int: Identifiable {
public var id: Self { self }
}
If UUID() is required, then use this instead:
extension Int: Identifiable {
public var id: String {
return UUID().uuidString
}
}
I'm lost, why Text("\(type)") would get compile error meantime Text(str) is not. Did that string interpolation not create a string?
For the error please check screenshot in below.
enum ExpenseType: Codable, CaseIterable {
case Personal
case Business
}
struct AddView: View {
#State private var type: ExpenseType = .Personal
let types: [ExpenseType] = ExpenseType.allCases
var body: some View {
Form {
...
Picker("Type", selection: $type) {
ForEach(types, id: \.self) { type in
let str = "\(type)"
Text(str)
// Compile error
Text("\(type)")
}
}
...
}
Xcode fails to detect which Text initializer should be used, a rather annoying bug.
Possible workarounds:
Using String(describing:) initializer:
Text(String(describing: type))
Declaring a variable at first:
let text = "\(type)"
Text(text)
You need to use rawValue, and try to loop more efficiently over the allCases.
enum ExpenseType: String, CaseIterable {
case Personal
case Business
}
struct ContentView: View {
#State var expenseType = ExpenseType.Personal
var body: some View {
List {
Picker(selection: $expenseType, label: Text("Picker")) {
ForEach(ExpenseType.allCases, id: \.self) { type in
Text(type.rawValue)
}
}
.pickerStyle(.inline)
}
}
}
It's probably a stupid question but I'm trying to allow all string enums as type for a variable in a Struct. The code below is completely wrong but I hope that it will make my question clearer.
My idea was to conform all enums to the same protocol but I can't access .allCases with this approach.
My goal is that I can pass any string enum to the ListView which will then display all components of the enum (here: one; two; three).
Does anybody have an idea how to do this? It must be a very basic Swift thing but I wasn't able to figure it out searching through the web. Thank you so much!
import SwiftUI
struct ContentView: View {
var body: some View {
ListView(myEnum: Elements.self) //error here as well
}
}
protocol StringRepresentable {
var rawValue: String { get }
}
enum Elements: String, Equatable, CaseIterable, StringRepresentable {
case one
case two
case three
}
struct ListView: View {
let myEnum: StringRepresentable //doesn't work
var body: some View {
ForEach(myEnum.allCases, id: \.self) { elem in //can't access .allCases
Text(elem.rawValue)
}
}
}
There's several errors in your original code. First you weren't using a VStack (or List or LazyVStack) so your foreach would only render one element.
Secondly, you were passing the type, not the elements itself to the list view. And finally, your own StringRepresentable protocol is unnecessary, you can use RawRepresentable with it's associated type RawValue constrained to String
i.e. something like this:
struct ContentView: View {
var body: some View {
VStack {
ListView(values: Fruits.allCases)
ListView(values: Animals.allCases)
}
}
}
enum Fruits: String, CaseIterable {
case apple
case orange
case banana
}
enum Animals: String, CaseIterable {
case cat
case dog
case elephant
}
struct ListView<T: RawRepresentable & Hashable>: View where T.RawValue == String {
let values: [T]
var body: some View {
LazyVStack {
ForEach(values, id: \.self) { elem in
Text(elem.rawValue)
}
}
}
}
Which renders like this
Here is a variant that will work by sending in all items of an enum to the view rather that the enum itself and making the view generic.
struct ContentView: View {
var body: some View {
ListView(myEnum: Elements.allCases)
}
}
protocol StringRepresentable {
var rawValue: String { get }
}
enum Elements: String, Equatable, CaseIterable, StringRepresentable {
case one
case two
case three
}
struct ListView<T: CaseIterable & StringRepresentable>: View {
let myEnum: [T]
#State private var selectedValue: String = ""
var body: some View {
ForEach(0..<myEnum.count) { index in
Text(myEnum[index].rawValue)
.onTapGesture {
selectedValue = myEnum[index].rawValue
}
}
}
}
I'm am trying to ForEach over a String Array inside of a Class and receiving an error:
Type '_' has no member 'name'
I'm trying to model off what I read in this tutorial and don't understand why mine is failing to compile.
https://www.hackingwithswift.com/books/ios-swiftui/working-with-identifiable-items-in-swiftui
struct StatisticItem: Codable {
let name: String
let startValue: Int
let modifier: Int
}
class Statistics: ObservableObject {
let statisticsNames = ["One", "Two"]
#Published var statList: [StatisticItem]
init() {
self.statList = [];
for statisticsName in statisticsNames {
self.statList.append(StatisticItem(name: statisticsName, startValue: Int.random(in: 20 ... 100), modifier: 0))
}
}
}
struct ContentView: View {
#ObservedObject var statistics = Statistics()
var body: some View {
Form {
List {
// Error shows up on this line: Type '_' has no member 'name'
ForEach(statistics.statList, id:\.name) { stat in
TextField(stat.name)
}
}
}
}
}
First of all your model needs to be modifiable (so used var instead of let)
struct StatisticItem: Codable {
var name: String
var startValue: Int
var modifier: Int
}
Second, TextField requires Binding, so it should be like below
ForEach(Array(statistics.statList.enumerated()), id:\.1.name) { (i, stat) in
TextField("", text: self.$statistics.statList[i].name)
}
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
}
}