SwiftUI computed var in custom init of a view error - swiftui

I want to be able to calculate a variable (nbwin) depending of the attribute "nom_rang1" of the coredata entity "RamiSession".
Basically, for each entity "RamiSession" whose attribute "nom_rang1" is equal to "nom_joueur_selec" (which is a String), I want to increment a counter and return the final value of that counter in the variable "nbwin". And then display it in a text box.
I think I have to compute that property "nbwin" in my custom init() method. But it doesn't work.
I have the error message on the line where nbwin is calculated in init saying "Function produces expected type 'Int'; did you mean to call it with '()'?"
But if I put () at the end of the calculation of "nbwin", I have an error message saying "'self' used before all stored properties are initialized"
So I guess I am doing something wrong...
Can anyone help me? Thank you
Here is my code
struct GraphView: View {
#FetchRequest
private var sessions: FetchedResults<RamiSession>
private var nbwin : Int
init(nom_joueur_selec: String) {
let predicate = NSPredicate(format: "nom_rang1= %# OR nom_rang2= %# ", nom_joueur_selec, nom_joueur_selec)
let sortDescriptors = [SortDescriptor(\RamiSession.date)] // need something to sort by.
_sessions = FetchRequest(sortDescriptors: sortDescriptors, predicate: predicate)
nbwin = {
var nbwinTotal = 0
for session in sessions {
if session.nom_rang1 == nom_joueur_selec {
nbwinTotal += 1
}
}
return nbwinTotal
}
}
//
var body: some View {
Text("\(nbwin)")
} // body
}

Related

in SwiftUI, I have 2 Entities (A & B) in my CoreData with a relationship (one to many) between them, how can I fetch all attributes of B in TextFields

Let's say I have 2 entities:
GameSession :which has Attributes "date", "place", "numberofplayer" + a relationship called "players" with "Player"
Player: which has Attributes "name","score_part1","score_part2","score_part3" + a relationship with "GameSession"
the relationship is "one to many": One session can have many players
Let's say now I have a list of GameSession and when I click on on one (with a NavigationLink)
It sends me to a new view where I can see:
All the names of the players of that session (in text) and also right next to the player name I would like to have 3 TextField in which I can enter (an update) "score_part1","score_part2","score_part3" for every players of that session
Basically I am able to display the name of all the players of a given session, But it seems impossible to have the "score_part1","score_part2","score_part3" in editable TextField...
I have an error saying "Cannot convert value of type 'String' to expected argument type 'Binding<String>'"
Basically in my first view I have something like that:
struct RamiListePartieUIView: View {#Environment(.managedObjectContext) var moc#FetchRequest(entity: GameSession.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \GameSession.date, ascending: false)]) var gamesessions: FetchedResults<GameSession>
var body: some View {
VStack {
List {
ForEach(gamesessions, id: \.date) { session in
NavigationLink (destination: DetailPartieSelecUIView(session: session)){
Text("\(session.wrappedPlace) - le \(session.wrappedDate, formatter: itemFormatter) ")
}
}
.onDelete(perform: deleteSessions)
.padding()
}
}
}
}
And in my second view I have something like that:
struct DetailPartieSelecUIView: View {
#State var session:GameSession
#Environment(\.managedObjectContext) var moc
var body: some View {
Section("Ma session du \(session.wrappedDate, formatter: itemFormatter)"){
ForEach(session.playersArray, id: \.self) { player in
HStack {
Text(player.wrappedName) // OK it works
TextField("score", text : player.wrappedScore_part1) // it generates an error
TextField("score", text : player.wrappedScore_part2) // it generates an error
TextField("score", text : player.wrappedScore_part3) // it generates an error
}
}
}
}
}
private let itemFormatter: DateFormatter = {
let formatter = DateFormatter()
// formatter.dateStyle = .short
// formatter.timeStyle = .medium
formatter.dateFormat = "YYYY/MM/dd" //"YY/MM/dd"
return formatter
}()
also,
I have defined the "wrappedScore_part1","wrappedScore_part2","wrappedScore_part3" in the Player+CoreDataProperties.swift file
and "wrappedPlace", "wrappedData" as well as the "PlayersArray" in the GameSession+CoreDataProperties.swift file
it is done like that:
public var wrappedPlace: String {
place ?? "Unknown"
}
// Convert NSSet into an array of "Player" object
public var playersArray: [Player] {
let playersSet = players as? Set<Player> ?? []
return playersSet.sorted {
$0.wrappedName< $1.wrappedName
}
}
I am new at coding with swiftUI so I am probably doing something wrong... If anyone can help me it would be much appreciated.
Thanks a lot
I have tried a lot of things. Like changing the type of my attribute to Int32 instead os String. As I am suppose to enter numbers in those fields, I thought it would be best to have Integer. But it didn't change anything. and ultimately I had the same kind of error message
I tried also to add the $ symbol, like that:
TextField("score", text : player.$wrappedScore_part1)
But then I had other error message popping up at the row of my "ForEach", saying "Cannot convert value of type '[Player]' to expected argument type 'Binding'"
And also on the line just after the HStack, I had an error saying "Initializer 'init(_:)' requires that 'Binding' conform to 'StringProtocol'"
Thank you for your help!
Best regards,
JB
Your first problem of how to fetch the players in a session you need to supply a predicate to the #FetchRequest<Player>, e.g.
#FetchRequest
private var players: FetchedResults<Player>
init(session: Session) {
let predicate = NSPredicate(format: "session = %#", session)
let sortDescriptors = [SortDescriptor(\Player.timestamp)] // need something to sort by.
_players = FetchRequest(sortDescriptors: sortDescriptors, predicate: predicate)
}
That acts like a filter and will only return the players that have the session relation equalling that object. The reason you have to fetch like this is so any changes will be detected.
The second problem about the bindings can be solved like this:
struct PlayerView: View{
#ObservedObject var player: Player {
var body:some View {
if let score = Binding($player.score) {
TextField("Score", score)
}else{
Text("Player score missing")
}
}
}
This View takes the player object as an ObservedObject so body will be called when any of its properties change and allows you to get a binding to property. The Binding init takes an optional binding and returns a non-optional, allowing you to use it with a TextField.

Creating a List of Toggle From a Set Using ForEach in swiftui

I' trying to create a set of Toggles, that need to be stored in one core data field of type "Transformable". I started with this example:
https://developer.apple.com/forums/thread/118595
in combination with other ideas from stack.
I'm trying to get this way:
Create a Set of structs like this
struct AllScopes: Identifiable, Hashable {
var id: UUID
var name: String
var notify: Bool
}
[...]
// all the stuff with View and body with
#State var scopes = Set<AllScopes>()
[...]
// and here I run through my FetchRequest to fill the Set
.onAppear {
for scope in allScopes {
scopes.insert(
AllScopes(
id: scope.id!,
name: scope.name!,
notify: false
)
)
}
}
In the end I've got a nice Set with all my scopes.
I call a new View with YearlyReportPage6(scopes: $scopes)
And now my problem - the next view:
struct YearlyReportPage6: View {
#Binding var scopes: Set<AllScopes>
init(scopes: Binding<Set<AllScopes>>) {
_scopes = scopes
}
var body: some View {
VStack {
ForEach(scopes.indices) { index in
Toggle(isOn: self.$scopes[index].notify) {
Text(self.scopes[index].name)
}
}
}
}
}
But all in ForEach creates errors. Either Binding in isOn: is wrong, or ForEach can't work with the set, or the Text is not a String, or, or, or...
In the end there should be a list of Toggles (checkboxes) and the selection should be stored in database.
Changing the Set into a simple Array like #State var scopes = [AllScopes]() will work for the Toggles, but how can I store this into a Transformable?
ForEach(Array(scopes).indices) { index in
Toggle(isOn: self.$scopes[index].notify) {
Text(self.scopes[index].name)
}
}
To summarize:
either how can I create the list of Toggles with the Set of AllScopes
or how can I store the Array / Dictionary into the Transformable field?
I hope, you can understand my clumsy English. :-)

Is it possible to create a binding property from a computed property in SwiftUI?

In my model data class I have a task array.
class Model: ObservableObject {
#Published var tasks: [Task] = Task.sampleData
}
In my HomeView I display these tasks however the user has the ability to use a picker to sort them by the date they were created or by the date they are due. In the HomeView I have an enum that contains the options that the user can sort them by and a state instance to hold the picked value. By default it is set to sort by due date.
#EnvironmentObject private var model: Model
#State private var sortOption: SortOption = .dueDate
enum SortOption {
case dueDate, creationDate
}
I decided to then create a computed value that contains the sorted array so I don't change the original tasks array as it is used to display tasks in other parts of the app.
var sortedTasks: [Task] {
model.tasks.sorted(by: {
if sortOption == .dueDate {
return $0.dueDate < $1.dueDate
} else {
return $0.creationDate < $1.creationDate
}
})
}
And soon after I ran into the problem. My Task Row requires a binding task and I am unable to retrieve the required binding from the computed property which was the sortedTasks array.
// ERROR: Cannot find '$sortedTasks' in scope
ForEach($sortedTasks) { $task in
TaskRow(task: $task)
}
My understanding is that if I want to keep the sortedTasks array then I will have to remove the binding property from the TaskRow but if I want to keep the binding property what should the approach be to display a sorted array without manipulating the tasks array in the data model class? Could the computed property return a sorted array of binding task objects?
The sort depends on sortOption which is view state so it is not correct to do it in the model because then you can't have different views using different sort options. The solution is to sort the bindings, not the tasks, e.g.
var sortedTasks: [Binding<Task>] {
$model.tasks.sorted { $x, $y in // $model.tasks.animation().sorted if you would like animations like List row moves.
if sortOption == .dueDate {
return x.dueDate < y.dueDate
} else {
return x.creationDate < y.creationDate
}
}
}
ForEach(sortedTasks) { $task in
TaskRow(task: $task)
}
You might be wondering how ForEach can find the id, well that's because Binding has #dynamicMemberLookup which means it forwards property lookups to its value.
You can´t do that but you can use a Combine approach here:
Move your sortedTasks, sortOption var to your class named Model and create a combined publisher from your two vars that should trigger the sortedTasks var to update. Sort the received collection and assign it to your sortedTasks.
class Model: ObservableObject {
#Published var tasks: [TaskModel] = TaskModel.sampleData
#Published var sortedTasks: [TaskModel] = TaskModel.sampleData
#Published var sortOption: SortOption = .dueDate
init(){
Publishers.CombineLatest($sortOption, $tasks)
.map{ sortOption, tasks in
// apply sorting here
}.assign(to: &$sortedTasks) // assign to your sorted var
}
}
Use it like:
var body: some View{
HStack{
ForEach($model.sortedTasks) { $task in
// TaskRow(task: $task)
}
}
}
I´ve renamed the Task model to TaskModel as i think it is a very bad idea to have a struct named after a system provided class.

SwiftUI #Binding Integer value check type error '() -> ()' cannot conform to 'ShapeStyle'

I'm wrapping my head around SwiftUI binding values and I'm stuck trying to check that an Integer doesn't equal 0. When using the following code I get the error type '() -> ()' cannot conform to 'ShapeStyle'. I've also tried wrapping the Int around an object but the same error happened. Can you only use binding for Bools?
#State private var contentToPresent: Int = 0
var body: some View {
VStack(spacing: 0) {
if contentToPresent.wrappedValue != 0 {
showView(id: $contentToPresent)
}
}
}
In SwiftUI we don't do any computation in the body method because it slows things down and can break structural identity. Instead, we do it in handlers and then set a state var that the body simply reads from. e.g.
#State private var present = false
let contentToPresent: Int
// body called when either this View is init again with a different contentToPresent or if present changes.
var body: some View {
VStack(spacing: 0) {
if present {
View2(contentToPresent: contentToPresent)
}
}
.onChange(of: contentToPresent) { newVal
present = ( newVal != 0 )
}
}
In child Views we use let properties when we only need read access and body will run every time the value changes. If you need write access you can change contentToPresent to be a #Binding var.
Note we usually use NavigationLink or sheet(item:) to present things.

How to use published optional properties correctly for SwiftUI

To provide some context, Im writing an order tracking section of our app, which reloads the order status from the server every so-often. The UI on-screen is developed in SwiftUI. I require an optional image on screen that changes as the order progresses through the statuses.
When I try the following everything works...
My viewModel is an ObservableObject:
internal class MyAccountOrderViewModel: ObservableObject {
This has a published property:
#Published internal var graphicURL: URL = Bundle.main.url(forResource: "tracking_STAGEONE", withExtension: "gif")!
In SwiftUI use the property as follows:
GIFViewer(imageURL: $viewModel.graphicURL)
My issue is that the graphicURL property has a potentially incorrect placeholder value, and my requirements were that it was optional. Changing the published property to: #Published internal var graphicURL: URL? causes an issue for my GIFViewer which rightly does not accept an optional URL:
Cannot convert value of type 'Binding<URL?>' to expected argument type 'Binding<URL>'
Attempting the obvious unwrapping of graphicURL produces this error:
Cannot force unwrap value of non-optional type 'Binding<URL?>'
What is the right way to make this work? I don't want to have to put a value in the property, and check if the property equals placeholder value (Ie treat that as if it was nil), or assume the property is always non-nil and unsafely force unwrap it somehow.
Below is an extension of Binding you can use to convert a type like Binding<Int?> to Binding<Int>?. In your case, it would be URL instead of Int, but this extension is generic so will work with any Binding:
extension Binding {
func optionalBinding<T>() -> Binding<T>? where T? == Value {
if let wrappedValue = wrappedValue {
return Binding<T>(
get: { wrappedValue },
set: { self.wrappedValue = $0 }
)
} else {
return nil
}
}
}
With example view:
struct ContentView: View {
#StateObject private var model = MyModel()
var body: some View {
VStack(spacing: 30) {
Button("Toggle if nil") {
if model.counter == nil {
model.counter = 0
} else {
model.counter = nil
}
}
if let binding = $model.counter.optionalBinding() {
Stepper(String(binding.wrappedValue), value: binding)
} else {
Text("Counter is nil")
}
}
}
}
class MyModel: ObservableObject {
#Published var counter: Int?
}
Result: