#EnvironmentObject Initializer 'init(_:)' requires that 'Binding<String>' conform to 'StringProtocol' - swift

I have an EnvironmentObject that I want to use as a datasource for my button title:
struct ContentView: View {
#State var showDetailsView = false
#EnvironmentObject var storage: Storage
var body: some View {
NavigationView {
ZStack {
Button(action: {
self.doSomethingAsync()
}) {
Text($storage.buttonTitle) // won't compile here
Here is my storage object:
class Storage: ObservableObject {
#Published var buttonTitle: String
#Published var dataObject: DataObject
init(dataObject: DataObject = DataObject(name: "Test")) {
self.dataObject = dataObject
buttonTitle = "Try"
}
}

Text takes in a String not a Binding<String>. Replace the line you pointed out with the following:
Text(storage.buttonTitle)

Related

How to observer a property in swift ui

How to observe property value in SwiftUI.
I know some basic publisher and observer patterns. But here is a scenario i am not able to implement.
class ScanedDevice: NSObject, Identifiable {
//some variables
var currentStatusText: String = "Pending"
}
here CurrentStatusText is changed by some other callback method that update the status.
Here there is Model class i am using
class SampleModel: ObservableObject{
#Published var devicesToUpdated : [ScanedDevice] = []
}
swiftui component:
struct ReviewView: View {
#ObservedObject var model: SampleModel
var body: some View {
ForEach(model.devicesToUpdated){ device in
Text(device.currentStatusText)
}
}
}
Here in UI I want to see the real-time status
I tried using publisher inside ScanDevice class but sure can to use it in 2 layer
You can observe your class ScanedDevice, however you need to manually use a objectWillChange.send(),
to action the observable change, as shown in this example code.
class ScanedDevice: NSObject, Identifiable {
var name: String = "some name"
var currentStatusText: String = "Pending"
init(name: String) {
self.name = name
}
}
class SampleViewModel: ObservableObject{
#Published var devicesToUpdated: [ScanedDevice] = []
}
struct ReviewView: View {
#ObservedObject var viewmodel: SampleViewModel
var body: some View {
VStack (spacing: 33) {
ForEach(viewmodel.devicesToUpdated){ device in
HStack {
Text(device.name)
Text(device.currentStatusText).foregroundColor(.red)
}
Button("Change \(device.name)") {
viewmodel.objectWillChange.send() // <--- here
device.currentStatusText = UUID().uuidString
}.buttonStyle(.bordered)
}
}
}
}
struct ContentView: View {
#StateObject var viewmodel = SampleViewModel()
var body: some View {
ReviewView(viewmodel: viewmodel)
.onAppear {
viewmodel.devicesToUpdated = [ScanedDevice(name: "device-1"), ScanedDevice(name: "device-2")]
}
}
}

How to bind a #publish datamodel in SwiftUI?

Is there any way to bind a data model in swiftui?
I have coded like below and need to build a struct so that I can use it in multiple views but the problem is to know how to bind a #publish data model in swiftui?
var birds: [PlayerItem] = [PlayerItem(id: UUID(), playershow: false)]
var dogs: [PlayerItem] = [PlayerItem(id: UUID(), playershow: true)]
class Controller: ObservableObject {
#Published var bird = birds
#Published var dog = dogs
}
struct PlayerItem: Hashable {
var id = UUID()
var playerShow: Bool
}
struct ContentView: View {
#EnvironmentObject var control: Controller
var body: some View {
setButton(isOn: $Controller.bird)
}
}
struct setButton: View {
#Binding var isOn: [PlayerItem]
var body: some View {
Button(action: {
self.isOn[0].toggle()
}) {
Text(isOn[0] ? "Off" : "On")
}
}
}
I wrote the following code:
#Binding var isOn: [PlayerItem]
However, it complained the following:
Value of type 'EnvironmentObject<controller>.Wrapper' has no dynamic member 'isOn' using the key path from the root type 'Controller'
try the following code, it shows how to use #Binding and how you have to use playershow
class Controller: ObservableObject {
#Published var bird = [Playeritem(id: UUID(), playershow: false)]
#Published var dog = [Playeritem(id: UUID(), playershow: true)]
}
struct Playeritem: Hashable {
var id = UUID()
var playershow: Bool
}
struct ContentView: View {
#StateObject var control = Controller() // <-- for testing
var body: some View {
setButton(isOn: $control.bird) // <-- here control
}
}
struct setButton: View {
#Binding var isOn: [Playeritem]
var body: some View {
Button(action: {
self.isOn[0].playershow.toggle() // <-- here playershow
}) {
Text(isOn[0].playershow ? "Off" : "On") // <-- here playershow
}
}
}

SwiftUI Loading Data

I am trying to load data from an ObservableObject class when I present a SwiftUI view but I get the following error code: "Cannot use instance member 'documentID' within property initializer; property initializers run before 'self' is available"
Class with the data querying:
class ItemDataDelegate: ObservableObject {
#Published var serviceContentLoaded = Bool()
#Published var SelectedCategory: String?
#Published var SelectedDocumentID: String?
init(SelectedCategory: String, SelectedDocumentID: String){
self.getSelectedContentData(Category: SelectedCategory, DocumentID: SelectedDocumentID) {
self.getSelectedCompanyData(CUID: ContentData[indexPath.row].CompanyID) {
print("data query completed")
}
}
}
}
Here is the View that I want to present when the data from the previous code is called and completed:
The error I mentioned above is shown on the #StateObject var itemDataDelegate = ItemDataDelegate(SelectedCategory: selectedCategory, SelectedDocumentID: documentID) line of code.
struct DetailView: View {
#Binding var documentID: String
#Binding var selectedCategory: String
#StateObject var itemDataDelegate = ItemDataDelegate(SelectedCategory: selectedCategory, SelectedDocumentID: documentID)
#State private var showSheet = false
#Environment(\.presentationMode) var mode: Binding<PresentationMode>
var image: UIImage
var companyName: String
var cartActive: Bool
var body: some View {
return ZStack {
switch itemDataDelegate.serviceContentLoaded {
case true:
ContentView()
case false:
LoadingView()
}
}
.navigationBarHidden(true)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.edgesIgnoringSafeArea(.all)
}
}
Since you need to use one of the passed-in parameters in the initialization of the #StateObject, you'll have to write a custom init for your View. Using your current code, it would look something like this:
struct DetailView: View {
#Binding var documentID: String
#Binding var selectedCategory: String
#StateObject var itemDataDelegate : ItemDataDelegate
#State private var showSheet = false
#Environment(\.presentationMode) var mode: Binding<PresentationMode>
var image: UIImage
var companyName: String
var cartActive: Bool
init(documentID: Binding<String>, selectedCategory: Binding<String>, image: UIImage, companyName: String, cartActive: Bool) {
_documentID = documentID
_selectedCategory = selectedCategory
self.image = image
self.companyName = companyName
self.cartActive = cartActive
_itemDataDelegate = StateObject(wrappedValue: ItemDataDelegate(SelectedCategory: selectedCategory.wrappedValue, SelectedDocumentID: documentID.wrappedValue))
}
var body: some View {
return ZStack {
switch itemDataDelegate.serviceContentLoaded {
case true:
ContentView()
case false:
LoadingView()
}
}
.navigationBarHidden(true)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.edgesIgnoringSafeArea(.all)
}
}

Passing data with ObservableObject keyword

How can I move data to other screens with ObservableObject keyword?
I save the data on the first page to the variable I created with the keyword Published, but I cannot access this data on the second page.
User Model
import Combine
struct User: Codable, Identifiable {
var id = UUID()
var name: String
var surName: String
}
UserDataStore
import Combine
class UserDataStore: ObservableObject {
#Published var users: [User] = []
}
ContentView
I get information from the user with TextField objects on the contentView screen. After pressing the button, I add it to the array in the UserDataStore. I redirect to the detail page.
struct ContentView: View {
#State var name: String = ""
#State var surName: String = ""
#State var user = User(name: "", surName: "")
#State var show: Bool = false
#ObservedObject var userStore = UserDataStore()
var body: some View {
NavigationView {
VStack(spacing: 50) {
TextField("isim", text: $name)
TextField("soyÄ°sim", text: $surName)
NavigationLink(
destination: DetailView(),
isActive: $show,
label: {
Button(action: {
self.user.name = name
self.user.surName = surName
self.userStore.users.append(user)
self.show = true
}) {
Text("Kaydet")
}
})
}
}
}
}
DetailView
On the detail page, I try to view the recorded information, but I cannot.
struct DetailView: View {
#ObservedObject var user = UserDataStore()
var body: some View {
ForEach(user.users) { item in
Text("\(item.name)")
}
}
}
Like #"New Dev" explained you're initializing a new instance of UserDataStore therefore your data isn't accessible from the DetailView.
You can use an EnvironmentObject to access the data from ContentView to DetailView.
In order to do this you would have to set the NavigationLinks destination to:
destination: DetailView().environmentObject(userStore)
Then you can access it from the DetailView like this:
#EnvironmentObject var user: UserDataStore

Init for a SwiftUI class with a #Binding var

I have a class which I want to initialize with a Binding var that is set in another View.
View ->
struct CoverPageView: View {
#State var numberOfNumbers:Int
var body: some View {
NavigationView {
GeometryReader { geometry in
VStack(alignment: .center, spacing: 0){
TextField("Multiplication Upto:", value: self.$numberOfNumbers, formatter: NumberFormatter())
}
}
}
}
CLASS WHICH NEEDS TO BE INITIALIZED USING THE #Binding var $numberofNumbers -
import SwiftUI
class MultiplicationPractice:ObservableObject {
#Binding var numberOfNumbers:Int
var classNumofNumbers:Int
init() {
self.classNumofNumbers = self.$numberOfNumbers
}
}
The init statement obviously gives the error that self is not initialized and the instance var is being used to initialize which is not allowed.
How do I circumvent this? The class needs to be initialized with the number the user enters on the first view. I have written approx. code here so ignore any typos please.
Typically you'd initialize MultiplicationPractice in CoverPageView with a starting value:
#ObservedObject var someVar = MultiplicationPractice(NoN:123)
And of course, add a supporting init statement:
class MultiplicationPractice:ObservableObject {
init(NoN: Int) {
self.numberOfNumbers = val
}
and you wouldn't want to wrap your var with #Binding, instead wrap it with #Published:
class MultiplicationPractice:ObservableObject {
#Published var numberOfNumbers:Int
...
In your particular case I would even drop the numberOfNumbers var in your CoverPageView, and instead use the direct variable of the above someVar:
struct CoverPageView: View {
//removed #State var numberOfNumbers:Int
#ObservedObject var someVar = MultiplicationPractice(123)
...
TextField("Multiplication Upto:", value: self.$someVar.numberOfNumbers, formatter: NumberFormatter())
You'll notice that I passed in the sub-var of the #ObservedObject as a binding. We can do this with ObservableObjects.
Edit
I see now what you're trying to do, you want to pass a binding along across your ViewModel, and establish an indirect connection between your view and model. While this may not be the way I'd personally do it, I can still provide a working example.
Here is a simple example using your struct names:
struct MultiplicationGame {
#Binding var maxNumber:String
init(maxNumber: Binding<String>) {
self._maxNumber = maxNumber
print(self.maxNumber)
}
}
class MultiplicationPractice:ObservableObject {
var numberOfNumbers: Binding<String>
#Published var MulGame:MultiplicationGame
init(numberOfNumbers: Binding<String> ) {
self.numberOfNumbers = numberOfNumbers
self.MulGame = MultiplicationGame(maxNumber: numberOfNumbers)
}
}
struct ContentView: View {
#State var someText: String
#ObservedObject var mulPractice: MultiplicationPractice
init() {
let state = State(initialValue: "")
self._someText = state
self.mulPractice = MultiplicationPractice(numberOfNumbers: state.projectedValue)
}
var body: some View {
TextField("put your text here", text: $someText)
}
}
Okay, I don't really understand your question so I'm just going to list a few examples and hopefully one of them will be what you're looking for.
struct SuperView: some View {
#State var value: Int = 0
var body: some View {
SubView(value: self.$value)
}
}
struct SubView: View {
#Binding var value: Int
// This is the same as the compiler-generated memberwise initializer
init(value: Binding<Int>) {
self._value = value
}
var body: some View {
Text("\(value)")
}
}
If I misunderstood and you're just trying to get the current value, do this
struct SuperView: some View {
#State var value: Int = 0
var body: some View {
SubView(value: self.value)
}
}
struct SubView: View {
let value: Int
// This is the same as the compiler-generated memberwise initializer
init(value: Int) {
self.value = value
}
var body: some View {
Text("\(value)")
}
}