Broken Binding and ObservedObject in SwiftUI - swift

I have a structure where at the end of AddTaskView_Preview: ..... there is { task in } in AddTaskView, exactly like this:
struct AddTaskView_Previews: PreviewProvider {
static var previews: some View {
AddTaskView{task in }, formVM: ToDoFormViewModel())
.environmentObject(DataStore())
}
}
This type does not allow the use of #Binding or #ObservedObject - formVM: ToDoFormViewModel() in my application, because the two values Binding and ObservedObject work for me in other views only with parentheses of type () and not { }!
What about that please? I normally have this code in my views:
ContentView(viewRouter: ViewRouter(), formVM: ToDoFormViewModel()) , but it doesn't fit inside the square brackets { } with { task in } in the above example.
How should I consume my ObservedObject in formVM: ToDoFormViewModel() with those { } ?

Related

SwiftUI 4.0 - Passing a Binding via .navigationDestination(for: , destination: )

How do I pass a Binding via the new .navigationDestination(for: , destination: )?
import SwiftUI
enum TestEnum: String, Hashable, CaseIterable {
case first, second, third
}
struct ContentView: View {
#State private var test: TestEnum = .first
var body: some View {
NavigationStack {
VStack {
NavigationLink(value: test, label: {
Text(test.rawValue)
})
}
// This does not work, as it won't allow me to use $caze
.navigationDestination(for: TestEnum.self, destination: { caze in
SecondView(test: $caze)
})
}
}
}
struct SecondView: View {
#Environment(\.presentationMode) var presentationMode
#Binding var test: TestEnum
var body: some View {
ForEach(TestEnum.allCases, id: \.self) { caze in
Button(action: {
test = caze
presentationMode.wrappedValue.dismiss()
}, label: {
Text(caze.rawValue)
})
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
In SwiftUI 3.0 I'd simply use:
NavigationLink(destination: SecondView(test: $test), label: {
Text(test.rawValue)
})
Is this still the correct approach, as we cannot pass a Binding yet?
Not really interested in complex workarounds like using an EnvironmentObject and passing an index, as the SwiftUI 3.0 approach works fine.
However, if there is a proper way of passing a Binding via .navigationDestination(for: , destination: ) I'll happily use it.
According to this doc:
https://developer.apple.com/documentation/charts/chart/navigationdestination(for:destination:)/
[WRONG: Where destination get passed from NavigationLink, but in the doc they use the NavigationLink("Mint", value: Color.mint) version of the NavigationLink. I don't know if this make any difference.
You are using the NavigationLink(value:label:)]
EDIT: I insist, after further investigation, that you are using the ViewModifier wrong.
Read the doc that I pointed above. The viewModifier signature is:
func navigationDestination<D, C>(
for data: D.Type,
destination: #escaping (D) -> C
) -> some View where D : Hashable, C : View
Note D is Hashable, not State
So, when you pass $caze you are not passing the #State var above, but the NavigationLink(value: test... that is not a #State wrapper. Is the generic D ... a single value, so $caze is not a Binding to the State.
You can pass normally $test, from that is a Binding to the State test. But the parameter inside the closure .navigationDestination( value in ... is not.

Bindable boolean in array that's togglable SwiftUI

I have an array in SwiftUI where it's an array of a struct that contains a boolean value which is bounded by a Toggle.
struct Blah {
#State var enabled = true
}
struct ContentView: View {
#State public var blahs: [Blah] = [
Blah(false)
]
var body : some View {
List(blahs) { blah in
Toggle(isOn: blah.$enabled)
}
}
}
the blahs arrays will have a button that will append more Blah objects. Xcode is telling me this though:
Accessing State's value outside of being installed on a View. This will result in a constant Binding of the initial value and will not update.
How should I change this? I don't think I'm applying the concept right.
#State should only be used on a View — it shouldn’t be used inside your model.
Once you’ve removed that, you can use the element binding syntax to get bindings to individual items on the List:
struct Blah : Identifiable {
var id = UUID()
var enabled = true
}
struct ContentView: View {
#State public var blahs: [Blah] = [
Blah(false)
]
var body : some View {
List($blahs) { $blah in
Toggle(isOn: $blah.enabled)
}
}
}

How to show a value present inside a function inside the body property in SwiftUI?

I am making an app that requires me to use an ml model and is performing its calculation to get the output from the ml model inside a function, now I need to display that constant which stores the final output of that ml model inside the body property of my swift UI view so that I can present it inside Text() maybe and modify the way it looks Lil bit
all of this code is inside a single swift file
here is all the code
after making the changes it's still showing some error
struct newView: View {
let model = AudiCar()
#ObservedObject var values: impData
#State var price = ""
var body: some View {
Text("calculated price is " + price)
.onAppear {
calculatePrice()
}
func calculatePrice() { <- this is where it showing error, saying " Closure containing a declaration cannot be used with function builder 'ViewBuilder' "
do {
let AudiCarOutput = try model.prediction(model: String(values.nameSelection), year: Double(values.yearSelection), transmission: String(values.transmisssionSelec), mileage: Double(values.mileage), fuelType: String(values.fuelSelection))
let price = String(AudiCarOutput.price)
}
catch {
}
}
}
struct newView_Previews: PreviewProvider {
static var previews: some View {
newView(values: impData())
}
}
try something like this:
struct NewView: View {
let model = AudiCar()
#ObservevedObject var values: impData
#State var price = ""
var body: some View {
Text("calculated price is " + price)
.onAppear {
calculatePrice()
}
}
func calculatePrice() {
do {
...
price = String(AudiCarOutput.price)
}
catch {
...
}
}
}

SwiftUI: How to Update NavigationView Detail Screens?

I've got an app that gets a list of vehicles from a REST backend server. It then uses that list to build a list of vehicles that can be tapped to show the details about one of them:
#State private var selectedVehicle: Vehicle?
#Binding var vehicles: [Vehicle]
List {
NavigationView {
ForEach( vehicles ) { vehicle in
NavigationLink( destination: VehicleDetailScreen( vehicle: vehicle ),
tag: vehicle,
selection: self.$selectedVehicle ) {
Text( vehicle.name )
}
}
}
}
struct VehicleDetailScreen: View {
var vehicle: Vehicle
var body: some View {
// Lots of rendering code omitted
}
}
So far, so good. This works nicely. The problem arises when we fetch updated information from the server. Updating the bound vehicles property works great for updating the list. But the detail screen is still showing data that's no longer relevant.
My first thought was just to pop the detail view off of the NavigationView. Unfortunately, SwiftUI doesn't provide any reliable way that I can find to do this in a two-column view on the iPad.
My next thought was that we needed to pass the vehicle in to VehicleDetailScreen as a #Binding too so that we can update it. But this is tough to do as well because we would need a reference to that binding so that we can cram updated values into it. The only way I can think of to do that would be to rework our network and model object code entirely so that it works like CoreData, keeping objects in memory and updating them with new values from the server, rather than generating new objects. This would be a good deal of effort, and obviously isn't something I'm keen to do if there's another option.
So I'm kind of stuck on this. Any thoughts/ideas/suggestions are very welcome!
Perhaps the concept of #Binding is somewhat confusing. From a #State var (parent view), to #Binding var (child view).
A struct Hashable to facilitate and reorder the elements of the array [Vehicle].
Something like this:
struct Vehicle: Hashable {
var name:String
//var otherItem: Any
}
struct ContentView: View {
#State var vehicle: Vehicle //the struct of your REST
#State var vehicles: [Vehicle] // the array of your REST
var body: some View {
List {
NavigationView {
ForEach(vehicles, id:\.self) { item in // loop the array to get every single item conform to the struct
NavigationLink( destination: VehicleDetailScreen(vehicle: self.$vehicle)) { // here to pass the binding
Text("\(self.vehicle.name)")
}
}
}
}
}
}
//detail view
struct VehicleDetailScreen: View {
#Binding var vehicle: Vehicle // here the binding
var body: some View {
Text("\(vehicle.name)")
}
}
If you want your detail views to update when data changes, you will have to make use of bindings.
As far as architecture goes, I would suggest to create so called Stores that hold data which can be used in multiple views. This, in combination with some static provider for Stores, makes it that you can easily access and modify data anywhere, and let your views update automatically.
When using UIKit, you would manually refresh data by calling reloadTable for instance. In SwiftUI this is not done. You could hypothetically manually trigger the view to update, but I would advice against this, as it is not the way SwiftUI was intended.
I've modified your code to show an example of this:
class StoreProvider {
static let carStore = CarStore()
}
class CarStore: ObservableObject {
#Published var vehicles: [Vehicle] = [Vehicle(id: "car01", name: "Porsche", year: 2016), Vehicle(id: "car02", name: "Lamborghini", year: 2002)]
}
struct Vehicle: Identifiable, Hashable {
let id: String
var name: String
var year: Int
}
struct CarOverview: View {
#ObservedObject var store = StoreProvider.carStore
#State var selectedVehicle: Vehicle?
var body: some View {
NavigationView {
List {
ForEach(store.vehicles.indices) { vehicleIndex in
NavigationLink(destination: VehicleDetailScreen(vehicle: self.$store.vehicles[vehicleIndex])) {
Text(self.store.vehicles[vehicleIndex].name)
}.onTapGesture {
self.selectedVehicle = self.store.vehicles[vehicleIndex]
}
}
}
}
}
}
struct VehicleDetailScreen: View {
#Binding var vehicle: Vehicle
func updateValues() {
vehicle.year = Int.random(in: 1990..<2020)
}
var body: some View {
VStack {
Text(vehicle.name)
Text("Year: ") + Text(vehicle.year.description)
}.onTapGesture(perform: updateValues)
}
}

SwiftUI Calling functions from other class

I am having a little trouble here: I have a class
class TempC {
func GetData(){
//do stuff
}
}
And in ContentView I want to call the function, but I can't manage to do it, I am getting errors...
struct ContentView: View {
var variable : TempC
variable.GetData()
var body: some View {
Text("Hello World")
}
}
Or in any other method. How can I call an external function now?
PS: The error that I get are on the line with variable.GetData() which are:
Consecutive declarations on a line must be separated by ";"
Expected "("in argument list of cantons declaration
Expected "{"in body of function declaration
Expected 'func' keyword in instance method declaration
Invalid redeclaration of 'variable()'
It's like it is expecting to create a new function not to get the one that is already existing.
Depending on what your going to do in that call there are options, ex:
Option 1
struct ContentView: View {
let variable = TempC()
init() {
variable.GetData()
}
var body: some View {
Text("Hello World")
}
}
Option 2
struct ContentView: View {
let variable = TempC()
var body: some View {
Text("Hello World")
.onAppear {
self.variable.GetData()
}
}
}
Similarly you can call it in .onTapGesture or any other, pass reference to your class instance during initialising, etc.