swiftui: problem with adding a property to data struct
Guessing here but var property : Property
should be
@Binding var property : Property
let
means that the variable won't change. @Binding
is a two-way connection.
You will connect them in your previous view with something like this.
ForEach($data.properties, id:\.id){ $property
YourViewName(property: $property)
}
This question marks denote the two-way connection. Without them you are not going to be able to change the variable.
SWIFTUI: Why adding @State to property doesn't update data model
This is similar to your previous question. Change @State var property : Property
to
It is all about the connections. You break them in a few places along the way I made some changes and put comments along the line.
import SwiftUI
struct MetersParentView: View {
//Change to StateObject
@StateObject var data = PropertyData()
var body: some View {
NavigationView {
List {
ForEach($data.properties, id:\.id) {$property in
NavigationLink(destination: NavigationLinkView(data: data, property: $property)) {
Text(property.name)
}
}
}
}
}
}
struct NavigationLinkView: View {
@ObservedObject var data : PropertyData
//Make Binding there is no connection without it
@Binding var property : Property
var body: some View {
TabView {
MetersView(data: data, property: $property)
.tabItem{
VStack {
Image(systemName: "scroll")
Text("Utitity")
}
}
}
}
}
struct MetersView: View {
@ObservedObject var data: PropertyData
@State var selectedMeter: Meter = .init()
//Change to Binding
//State is a source of truth, it breaks the connection
@Binding var property : Property
@State private var addMeters = false
var body: some View {
VStack {
HStack {
Picker("Meters", selection: $selectedMeter) {
//Use object not index
ForEach(property.meters, id:\.id) {meter in
//Tag adjustment
Text(meter.name).tag(meter as Meter)
}
}.onAppear(perform: {
selectedMeter = property.meters.first ?? Meter()
})
.pickerStyle(SegmentedPickerStyle())
Button{
print(property.meters)
addMeters.toggle()
} label: {
Image(systemName: "gear")
}
}.padding()
Spacer()
}.sheet(isPresented: $addMeters){
AddMetersView(data: data, property: $property)
}
}
}
struct AddMetersView: View {
@ObservedObject var data : PropertyData
@Binding var property : Property
@State var newMeter: String = ""
@Environment(\.presentationMode) var presentationMode
var body: some View {
VStack {
Form{
Section {
TextField("Add another meter", text: $newMeter)
.autocapitalization(.none)
Button{
if newMeter != "" {
print(property.meters.count)
property.meters.append(Meter(name: newMeter))
print(property.meters.count)
data.save()
print(property.meters.count)
}
} label: {
Text("Add a meter")
}
}
//Dont use index
ForEach(property.meters, id:\ .id) {meter in
Text(meter.name)
}
Section() {
Button("That's enough"){
print(property.meters)
presentationMode.wrappedValue.dismiss()}
}
}
}
}
}
struct MetersParentView_Previews: PreviewProvider {
static var previews: some View {
MetersParentView()
}
}
Swift/SwiftUI property initializer error in struct
You are using name
before the struct is initialized. If you make message a computed properties it should work.
struct Greeting {
var name = "Bob"
var message: String {
"Hi, " + name
}
}
Mutating error when attempting to append an array of a struct
The issue is that a struct
cannot change its own properties without the function that is changing those properties being marked mutating
. You can’t mark body
as mutating
, but you can make children
a @State var
. @State
variables are mutable, but only from within your view’s body
property.
SwiftUI: Binding on property of struct derived from environment object
No, you're not necessarily doing something against best practices. I think in SwiftUI, the concepts of data model storage and manipulation quickly become more complex than, for example, what Apple tends to show in its demo code. With a real app, with a single source of truth, like you seem to be using, you're going to have to come up with some ways to bind the data to your views.
One solution is to write Binding
s with your own get
and set
properties that interact with your ObservableObject
. That might look like this, for example:
struct TaskView : View {
var taskIdentifier: String // Passed from parent view
@EnvironmentObject private var taskStore: TaskStore
private var taskBinding : Binding<Task> {
Binding {
taskStore.tasks[taskIdentifier] ?? .init(identifier: "", title: "", tags: [])
} set: {
taskStore.tasks[taskIdentifier] = $0
}
}
var body: some View {
TextField("Task title", text: taskBinding.title)
}
}
If you're averse to this sort of thing, one way to avoid it is to use CoreData. Because the models are made into ObservableObject
s by the system, you can generally avoid this sort of thing and directly pass around and manipulate your models. However, that doesn't necessarily mean that it is the right (or better) choice either.
You may also want to explore TCA which is an increasingly popular state management and view binding library that provides quite a few built-in solutions for the type of thing you're looking at doing.
Changes to a struct not being published in SwiftUI
This is caused by the:
@ObservedObject var weatherViewModel = WeatherViewModel()
being owned by the WeatherView
itself.
So what happens is the weather view model changes which forces a re-render of the view which creates a new copy of the weather view model, which changes forces a re-render...
So you end up with an endless loop.
To fix it you need to move the weather view model out of the view itself so either use an @Binding
and pass it in or an @EnvironmentObject
and access it that way.
setting computed property in a SwiftUI view doesn't compile
Aha... I see. Just use non mutating set
private var s : String
{ get { _s }
nonmutating set (new)
{ _s = "no test"
_s2 = false
// do something else
}
}
SwiftUI - Add values from two textfields using var or let
Use a computed-property
.
private var sumValues: Int { (Int(value1) ?? 0) + (Int(value2) ?? 0) }
Swift Struct with Lazy, private property conforming to Protocol
Because accessing the lazy data
variable mutates AStruct
, any access to it must be marked as also mutating the struct. So you need to write:
struct AStruct : Example {
// ...
var var1: String {
// must declare get explicitly, and make it mutating:
mutating get {
// act of looking at data mutates AStruct (by possibly initializing data)
return self.data.var1
}
}
var varArray:[String] {
mutating get {
return self.data.varArray
}
}
}
However, you'll find now that Swift complains you aren't conforming to Example
, because its get var1
isn't marked as mutating. So you'd have to change it to match:
protocol Example {
var var1:String { mutating get }
var varArray:[String] { mutating get }
}
Related Topics
Resizing Large Resolution Images Producing 1000X1000 Pixels Size When Size Is Set to 500X500 Pixels
Error "{ Expected After If Statement"
Can't Change Uiimageview Image in Function (Swift)
Binary Operator '==' Cannot Be Applied to Operands of Type 'Uilabel' and 'String'
Differencebetween Convenience Init VS Init in Swift, Explicit Examples Better
Using the Swift If Let with Logical and Operator &&
What Does an Exclamation Mark in a Property in Swift Language
What Is the Shortest Way to Run Same Code N Times in Swift
How to Use a Switch Statement with a Nested Enum
Color Ouput with Swift Command Line Tool
Receiving Data from Nsinputstream in Swift
Animated Curve Line in Swift 3
How to Find a Maximum Value in a Swift Dictionary
(Cross-)Compiling Swift for Raspberry Pi
Structuring Data for Chat App in Firebase
Swift Optional Variable Assignment with Default Value (Double Question Marks)