How to initialize state variable with core data model - swiftui

I don't know how to initialize a CoreData model in my ContentView:
This is the declaration line so far:
#State var selectedFriend: Friend
I want to use the type Friend in a picker but I don't know how to setup a custom picker so it works with a core data model.
Any thoughts or suggestions?
Thank you.

If you fetch the data with #FetchRequest and you want to display the data in a picker (no empty selection) my suggestion is to work with indices. A scalar value type can be initialised directly.
#State private var selectedFriendIndex = 0
...
Picker("Friend", selection: $selectedFriendIndex) {
ForEach(0..<friends.count, id: \.self) { index in
Text(friends[index].name)
}
}

Related

Binding<String> action tried to update multiple times per frame in SwiftUI

I have a VM that is implemented as follows:
LoginViewModel
class LoginViewModel: ObservableObject {
var username: String = ""
var password: String = ""
}
In my ContentView, I use the VM as shown below:
#StateObject private var loginVM = LoginViewModel()
var body: some View {
NavigationView {
Form {
TextField("User name", text: $loginVM.username)
TextField("Password", text: $loginVM.password)
Every time I type something in the TextField it shows the following message in the output window:
Binding<String> action tried to update multiple times per frame.
Binding<String> action tried to update multiple times per frame.
Binding<String> action tried to update multiple times per frame.
It is a message and not an error.
If I decorate my username and password properties with #Published then the message goes away but the body is rendered each time I type in the TextField.
Any ideas what is going on and whether I should use #Published or not. I don't think I will gain anything from putting the #Published attribute since this is a one-way binding and I don't want to display anything on the view once the username changes.
If I decorate my username and password properties with #Published then the message goes away
This is the correct solution. You need to use #Published on those properties because that is how SwiftUI gets notified when the properties change.
the body is rendered each time I type in the TextField
That is fine. Your body method is not expensive to compute.
I don't think I will gain anything from putting the #Published attribute since this is a one-way binding
You cannot be sure SwiftUI will work correctly (now or in future releases) if you don't use #Published. SwiftUI expects to be notified when the value of a Binding changes, even when a built-in SwiftUI component like TextField causes the change.
For the simple case - the state is kept in the same view or in a ModelSupport class, consists of strings or other primitive types, and there's only one of each, #Published will work fine.
I got this error with a model class containing an array of structs and using a List, and every time you type inside a TextField inside a list (or every time you select an item in a list), the view gets refreshed, and the error gets triggered.
I am thus using a DelayedTextField:
struct DelayedTextField: View {
var title: String = ""
#Binding var text: String
#State private var tempText: String = ""
var body: some View {
TextField(title, text: $tempText, onEditingChanged: { editing in
if !editing {
$text.wrappedValue = tempText
}
})
.onAppear {
tempText = text
}
}
}
and the binding update error is no more.

Swiftui: #environmentObject - Cannot use instance member within property initializer; property initializers run before 'self' is available

I try to pass data from an #environmentObject to a #State object in the TopLevel
struct ContentView: View {
#EnvironmentObject var countRecognizer: themeCounter
#State var theme: themeModel = themeData[countRecognizer.themeCount]
#State var hideBar = true
var body: some View {
ZStack {
videoCard(theme: theme)
.statusBar(hidden: true)
Text("\(self.countRecognizer.themeCount)")
if hideBar == true {
}
}
But I am getting this error: "Cannot use instance member within property initializer; property initializers run before 'self' is available"
the themeData Array should get the Int from the environment Object.
How can I fix this problem?
do your
theme: themeModel = themeData[countRecognizer.themeCount]
in
.onAppear(...)
You cannot use countRecognizer directly from the initial value of another property, and there is no easy fix.
I suggest you to look into refactoring your #State var theme property into a #Published var theme inside the themeCounter ObservableObject. Apple tutorials will help you: https://developer.apple.com/tutorials/swiftui/tutorials
As an aside: DON'T NAME TYPES WITH A LOWERCASE.
themeModel should be ThemeModel
themeCounter should be ThemeCounter
videoCard should be VideoCard

onReceive in SwiftUI view causes infinite loop

In a SwiftUI app, I have an ObservableObject that keeps track of user settings:
class UserSettings: ObservableObject {
#Published var setting: String?
}
I have a view model to control the state for my view:
class TestViewModel: ObservableObject {
#Published var state: String = ""
}
And I have my view. When the user setting changes, I want to get the view model to update the state of the view:
struct HomeView: View {
#EnvironmentObject var userSettings: UserSettings
#ObservedObject var viewModel = TestViewModel()
var body: some View {
Text(viewModel.state)
.onReceive(userSettings.$setting) { setting in
self.viewModel.state = setting
}
}
}
When the UserSettings.setting is changed in another view it causes onReceive on my view to get called in an infinite loop, and I don't understand why. I saw this question, and that loop makes sense to me because the state of the ObservableObject being observed is being changed on observation.
However, in my case I'm not changing the observed object (environment object) state. I'm observing the environment object and changing the view model state which redraws the view.
Is the view redrawing what's causing the issue here? Does onReceive get called everytime the view is redrawn?
Is there a better way of accomplishing what I'm trying to do?
EDIT: this is a greatly simplified version of my problem. In my app, the view model takes care of executing a network request based on the user's settings and updating the view's state such as displaying an error message or loading indicator.
Whenever you have an onReceive with an #ObservedObject that sets another (or the same) published value of the #ObservedObject you risk creating an infinite loop if those published attributes are being displayed somehow.
Make your onReceive verify that the received value is actually updating a value, and not merely setting the same value, otherwise it will be setting/redrawing infinitely. In this case, e.g.,:
.onReceive(userSettings.$setting) { setting in
if setting != self.viewModel.state {
self.viewModel.state = setting
}
}
From described scenario I don't see the reason to duplicate setting in view model. You can show the value directly from userSettings, as in
struct HomeView: View {
#EnvironmentObject var userSettings: UserSettings
#ObservedObject var viewModel = TestViewModel()
var body: some View {
Text(userSettings.setting)
}
}
You might be able to prevent infinite re-rendering of the view body by switching your #ObservedObject to #StateObject.

Implementing business and other logic in SwiftUI

(This is my first SwiftUI project; please be kind if this is a stupid question.)
I have a collection of objects which are displayed in a Picker. The picker selection is $selectedIndex, where
#State private var selectedIndex: Int = 0
I also have a
#State private var opts: OptsStruct = OptsStruct()
where elements of the OptsStruct structure are bound to SwiftUI views. The value of opts needs to change when the selectedIndex changes, because the opts property is the option shown in and selected by the Picker. (Also, I want to save the current value of selectedIndex in UserDefaults.) The problem is that I don't understand how to express these actions in SwiftUI.
I tried
#State private var selectedIndex: Int = 0 {
mutating didSet {
// save selectedIndex to UserDefaults
opts = f(selectedIndex)
}
but this causes a Segmentation Fault.
Where is the 'correct' place to put this logic. (And in general, can someone suggest some reading on how to connect changes to SwiftUI #States with general business logic.)
Thanks,
Rick
The idea of a #State variable is for it to be the single source of truth (wikipedia). This means that one variable should be the only thing that contains the "state" of your picker. In this case, I suggest using this:
$opts.selectionIndex
as the Binding for your picker. selectionIndex would then be a Int property of your OptsStruct type.

Init a class from my second view with data from the first

In my initial ContentView() I have a button that presents a UIImagePicker, when an image is chosen I then navigate to SecondView where I can view the image and it’s data from UIImagePickerController.InfoKey
I currently have an ImageManager class that’s set as an EnviromentObject that I pass the InfoKey to that then sets up all the variables in that class — it works but this feels messy.
What I’d like to do is init the ImageManager class when I navigate to SecondView as that’s the only view that needs that data.
I’d tried passing the InfoKey as a variable:
#State var InfoKey: [IImagePickerController.InfoKey: Any]
SecondView(key: self.infoKey)
but this crashes because I don’t have any data until an image is chosen
What would be the best way to tackle this?
#State must have initial value, so use just empty container
#State var infoKey: [IImagePickerController.InfoKey: Any] = [:]
then pass it in SecondView as binding
SecondView(key: self.$infoKey)
where
struct SecondView: View {
#Binding var key: [IImagePickerController.InfoKey: Any]