How to fix Xcode error "Segmentation fault: 11" after adding didSet to #State var - swift

I want to add a "didSet" function to a parameter of a SwiftUI's View struct, but every time I try to build the app I get the "Segmentation fault: 11" error.
I tried to rename the parameter, but nothing happened. I also tried to make it Optional but because it's a #State it didn't worked. What can I do?
#State var text: String {
didSet {
print(oldValue, text)
}
}

Try adding a default value to your var, which is necessary when defining a #State var.
#State var text: String = "" {
didSet {
print(oldValue, text)
}
}

I have this issue too, seems like a compiler bug or something. I have done some digging and found a bug raised by Apple which can be found here https://bugs.swift.org/browse/SR-10918
Instead of using didSet on a variable with a #State property wrapper you could have a view model that conforms to BindableObject (part of Combine) and use #ObjectBinding in your view so when anything within your view model is updated SwiftUI will update your UI
Here is a nice tutorial on how to do so...
https://www.hackingwithswift.com/quick-start/swiftui/how-to-use-objectbinding-to-create-object-bindings

Related

Swift binding to a computed property

Have the following situation. I have a view model that is an observable object with a computed property of type Bool. I want to be able to enable/disable a navigation link based on the computed property, but I need a binding to do so. Here a simplified example:
struct Project {
var name: String
var duration: Int
}
class MyViewModel: Observable Object {
#Published var project: Project
var isProjectValid: Bool {
return project.name != "" && project.duration > 0
}
}
struct MyView: View {
#EnvironmentObject var myVM: MyViewModel
var body: some View {
...
NavigationLink("Click Link", isActive: ?????, destination: NextView())
...
}
}
Since isActive expects a binding I am not able to access computed property such as myVM.isProjectValid. Tried also with the computed property in project class, still same problem.
Also considered creating custom binding related to the computed property, but not quite sure if/how to go about it.
First question I have posted, so if I am missing some details please be kind :)
Make it a #Published property and update it when project is changed
class MyViewModel: ObservableObject {
#Published var project: Project {
didSet {
isProjectValid = project.name != "" && project.duration > 0
}
}
#Published var isProjectValid: Bool
//...
}
The use of the computed property suggests some type of design where the user is not supposed to trigger the NavigationLink directly. But instead the NavigationLink is expected to be triggered programatically as a side-effect of some other mechanism elsewhere in the code. Such as might be done at the completion of a form or similar process by the user.
Not 100% if this is what's being aimed for, but if it is, then one option would be to pass a constant Binding to the NavigationLink, e.g.
NavigationLink("Click Link", isActive: .constant(myVM.isProjectValid), destination: NextView())`

SwiftUI: Pass value to and use it in init of child view

I've been trying to create a small calendar app with SwiftUI and ran into some issues while trying to pass a value to a child view and use it in its init.
My code looks like this:
ContentView (parent view):
struct ContentView: View {
#State var selectedMonth: Date
var body: some View {
MonthGridView(selectedMonth: $selectedMonth)
}
}
MonthGridView (child view):
struct MonthGridView: View {
#Binding private var selectedMonth: Date
var days: [Int]
//this is the part where I'm having troubles
init(selectedMonth: Binding<Date>) {
self._selectedMonth = selectedMonth
days = dayIndices(currentMonth: $selectedMonth) //custom function, also in this line is the error right now
}
var body: some View {
//code
}
}
I have looked through a lot of posts on here and a wide variety of tutorials and this is what I came up with. I've tried moving some code around, but wasn't able to get it fully working. I imagine the problem is somewhere around the init, maybe about the Binding wrapper, but I was unable to find information about how to unwrap it.
Appreciate any help getting this working.
It'll be easier to understand the problem if we “de-sugar” the #Binding property wrapper. When you say this:
#Binding private var selectedMonth: Date
Swift translates that into this:
private var _selectedMonth: Binding<Date>
private var $selectedMonth: Date { _selectedMonth.projectedValue }
private var selectedDate: Date {
get { _selectedMonth.wrappedValue }
nonmutating set { _selectedMonth.wrappedValue }
}
Here is your init again:
init(selectedMonth: Binding<Date>) {
self._selectedMonth = selectedMonth
days = dayIndices(currentMonth: $selectedMonth) //custom function, also in this line is the error right now
}
You're using $selectedMonth before days has been initialized. But as I showed above, $selectedMonth is a computed property. You are not allowed to call any methods on self before self is fully initialized, and the getter of a computed property counts as a method.
In general, you can work around the limitation by accessing _selectedMonth.projectedValue directly:
days = dayIndices(currentMonth: _selectedMonth.projectedValue)
However, in this case, all of _selectedMonth, _selectedMonth.projectedValue, and the init parameter selectedMonth are the same Binding, you can use any of them directly. So either of these will also work:
days = dayIndices(currentMonth: selectedMonth)
days = dayIndices(currentMonth: _selectedMonth)

SwiftUI not being updated with manual publish

I have a class, a “clock face” with regular updates; it should display an array of metrics that change over time.
Because I’d like the clock to also be displayed in a widget, I’ve found that I had to put the class into a framework (perhaps there’s another way, but I’m too far down the road now). This appears to have caused a problem with SwiftUI and observable objects.
In my View I have:
#ObservedObject var clockFace: myClock
In the clock face I have:
class myClock: ObservableObject, Identifiable {
var id: Int
#Publish public var metric:[metricObject] = []
....
// at some point the array is mutated and the display updates
}
I don’t know if Identifiable is needed but it’s doesn’t make any difference to the outcome. The public is demanded by the compiler, but it’s always been like that anyway.
With these lines I get a runtime error as the app starts:
objc[31175] no class for metaclass
So I took off the #Published and changed to a manual update:
public var metric:[metricObject] = [] {
didSet {
self.objectWillChange.send()`
}
}
And now I get a display and by setting a breakpoint I can see the send() is being called at regular intervals. But the display won’t update unless I add/remove from the array. I’m guessing the computed variables (which make up the bulk of the metricObject change isn’t being seen by SwiftUI. I’ve subsequently tried adding a “dummy” Int to the myClock class and setting that to a random value to trying to trigger a manual refresh via a send() on it’s didSet with no luck.
So how can I force a periodic redraw of the display?
What is MetricObject and can you make it a struct so you get Equatable for free?
When I do this with an Int it works:
class PeriodicUpdater: ObservableObject {
#Published var time = 0
var subscriptions = Set<AnyCancellable>()
init() {
Timer
.publish(every: 1, on: .main, in: .default)
.autoconnect()
.sink(receiveValue: { _ in
self.time = self.time + 1
})
.store(in: &subscriptions)
}
}
struct ContentView: View {
#ObservedObject var updater = PeriodicUpdater()
var body: some View {
Text("\(self.updater.time)")
}
}
So it's taken a while but I've finally got it working. The problem seemed to be two-fold.
I had a class defined in my framework which controls the SwiftUI file. This class is sub-classed in both the main app and the widget.
Firstly I couldn't use #Published in the main class within the framework. That seemed to cause the error:
objc[31175] no class for metaclass
So I used #JoshHomman's idea of an iVar that's periodically updated but that didn't quite work for me. With my SwiftUI file, I had:
struct FRMWRKShape: Shape {
func drawShape(in rect: CGRect) -> Path {
// draw and return a shape
}
}
struct ContentView: View {
#ObservedObject var updater = PeriodicUpdater()
var body: some View {
FRMWRKShape()
//....
FRMWRKShape() //slightly different parameters are passed in
}
}
The ContentView was executed every second as I wanted, however the FRMWRKShape code was called but not executed(?!) - except on first starting up - so the view doesn't update. When I changed to something far less D.R.Y. such as:
struct ContentView: View {
#ObservedObject var updater = PeriodicUpdater()
var body: some View {
Path { path in
// same code as was in FRMWRKShape()
}
//....
Path { path in
// same code as was in FRMWRKShape()
// but slightly different parameters
}
}
}
Magically, the View was updated as I wanted it to be. I don't know if this is expected behaviour, perhaps someone can say whether I should file a Radar....

A #State static property is being reinitiated without notice

I have a view which looks like this:
struct Login: View {
#State static var errorMessage = ""
init() {
// ...
}
var body: some View {
// ...
}
}
I set errorMessage as static so I can set an error message from anywhere.
The problem is that even being static, it is always reinitiated each time the login view is showed, so the error message is always empty. I was thinking that maybe the presence of the init() method initiate it somehow, but I didn't figure how to fix this. What can I do?
I set errorMessage as static so I can set an error message from anywhere.
This is a misunderstanding of #State. The point of #State variables is to manage internal state to a View. If something external is even looking at a #State variable, let alone trying to set it, something is wrong.
Instead, what you need is an #ObservableObject that is passed to the view (or is accessed as a shared instance). For example:
class ErrorManager: ObservableObject {
#Published var errorMessage: String = "xyz"
}
This is the global thing that manages the error message. Anyone can call errorManager.errorMessage = "something" to set it. You can of course make this a shared instance if you wanted by adding a property:
static let shared = ErrorManager()
With that, you then pass it to the View:
struct Login: View {
#ObservedObject var errorManager: ErrorManager
var body: some View {
Text(errorManager.errorMessage)
}
}
Alternately, you could use the shared instance if you wanted:
#ObservedObject var errorManager = ErrorManager.shared
And that's it. Now change to the error automatically propagate. It's more likely that you want a LoginManager or something like that to handle the whole login process, and then observe that instead, but the process is the same.
#State creates a stateful container that is associated with an instance of a view. Each instance of your view has it's own copy of that #State container.
In contrast, a static variable does not change across instances.
These two concepts are not compatible. You should not be using static with #State.

What is the property / $property syntax for bindings?

As seen in the handling user input tutorial.
struct LandmarkList: View {
#State var showFavoritesOnly = true
var body: some View {
NavigationView {
List {
Toggle(isOn: $showFavoritesOnly) {
Text("Favorites only")
}
...
What is the showFavoritesOnly / $showFavoritesOnly syntax ?
Is it something unique to Binding<T> or can we use it in our own code ?
#State is designed to be used as a binding for SwiftUI properties. Any access to it outside the body accessor of your View will crash with:
Thread 1: Fatal error: Accessing State<Bool> outside View.body
SwiftUI automatically tracks all the #State declarations and re-calculates the appropriate body whenever any of them change.
#State is implemented using the Swift 5.1 #propertyDelegate feature, which enables the storage behavior of properties to be customized.