I am new to SwiftUI and created a Todo model. I then have a class that represents the container so that I can have an array of Todos and methods on it later on. I made TodoContainer an ObservableObject and then observing it after instantiating it: #ObservedObject var items = TodoContainer().
I am iterating through the list and expecting anytime I click the button, shouldn't it appear in the list since it's creating a new Todo each time and allTodos should have the array of all Todos?
import SwiftUI
struct Todo: Identifiable {
var id = UUID()
var name: String;
var priority: Int;
}
class TodoContainer: ObservableObject {
#Published var allTodos = [Todo]();
}
struct ContentView: View {
#ObservedObject var items = TodoContainer()
var body: some View {
Button("Create New") {
Todo(name: "New one", priority: Int.random(in: 1..<100))
}
List(items.allTodos, id: \.id){item in
Text(item.name)
}
}
}
You need to add the created todo to the view model's allTodos. Xcode may have shown you an error about an unused expression previously.
import SwiftUI
struct Todo: Identifiable {
var id = UUID()
var name: String;
var priority: Int;
}
class TodoContainer: ObservableObject {
#Published var allTodos = [Todo]();
}
struct ContentView: View {
#ObservedObject var items = TodoContainer()
var body: some View {
Button("Create New") {
let todo = Todo(name: "New one", priority: Int.random(in: 1..<100))
items.allTodos.append(todo) // here
}
List(items.allTodos, id: \.id){item in
Text(item.name)
}
}
}
Related
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")]
}
}
}
I have this simple code for my model:
import Foundation
class TaskListModel: ObservableObject
{
struct TodoItem: Identifiable
{
var id = UUID()
var title: String = ""
}
#Published var items: [TodoItem]?
//MARK: - intents
func addToList()
{
self.items!.append(TodoItem())
}
}
Then I use it in this view:
import SwiftUI
struct TasksListView: View {
#ObservedObject var model = TaskListModel()
var body: some View {
List {
Button("Add list", action: {
model.addToList()
})
ForEach(model.items!) { item in
TextField("Title", text: item.title)
}
.onMove { indexSet, offset in
model.items!.move(fromOffsets: indexSet, toOffset: offset)
}
.onDelete { indexSet in
model.items!.remove(atOffsets: indexSet)
}
}
}
}
struct TasksListView_Previews: PreviewProvider {
static var previews: some View {
TasksListView()
}
}
I can't seem to make this code work, I suspect the items array needs to be wrapped in #Binding property wrapper, but it already wrapped in #Published, so it puzzles me even more. Any help would be appreciated!
You have forgotten to create array for items
class TaskListModel: ObservableObject
{
struct TodoItem: Identifiable
{
var id = UUID()
var title: String = ""
}
#Published var items: [TodoItem] = [] // << here !!
// ...
}
and remove everywhere force-unwrap (!!)
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
}
}
}
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
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)")
}
}