objectWillChange.send() how exactly does this work? [closed] - swift

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 8 months ago.
Improve this question
func choose(_ card: MemoryGame<String>.Card)
{
objectWillChange.send()
model.choose(card)
}
I have some function here which is in the ViewModel for something I'm making and I just wanna know how exactly does objectWillChange work? Like when it says object what exactly is it referring to? Since I put this in the ViewModel is it saying the ViewModel will change? But then again I put it specifically into this choose function so what relevance does that have? I basically want it to publish that something changed in the model(A card got chosen) which it seems to be doing but I don't fully understand what I stated previously.

objectWillChange is a property defined on the ObservableObject protocol as you can see in the documentation of ObservableObject.
The compiler synthesises a default implementation for the property, which emits a value whenever any #Published properties of the object changes.
SwiftUI uses the objectWillChange Publisher to update the View when you store an object as #ObservedObject (or #StateObject or #EnvironmentObject) on the view.
You can also manually send updates through objectWillChange using its send() method when you need to manually update the view.
See a simplified example below:
class ViewModel: ObservableObject {
#Published var published: Int = 0
var nonPublished: Int = 0
}
struct MyView: View {
#ObservedObject var viewModel: ViewModel
...
}
let viewModel = ViewModel()
let view = MyView(viewModel: viewModel)
viewModel.published = 1 // viewModel.objectWillChange emits a value here and view will be updated
viewModel.nonPublished = 2 // viewModel.objectWillChange does not emit a value here and view will not be updated

Related

SwiftUI: How could I use polymorphism to make a model class create its own View

I am trying to build a small App in which you can create tests with questions using SwifUI, but finding how to make everything works out is getting hard for a newbie like me. The app would show a list of questions in a main scrollable view and these questions could be of different types such as true or false, text, multiple choice, etc… and could be active or not.
I thought it would be great that all different types of questions adopted the same protocol. This protocol would also define a function or a computed property in charge of display its on view using the values store in the different attributes. However, the problem comes up when trying to modify any of this parameters interacting with that View. Let's say I want to add a toggle button that active or reactive the question, modifying one of the values of that question. With the different solutions I implemented, I didn't get the view being rebuild/updated.
I tried several things to accomplish this, like wrapping those properties that are supposed to update their values with #State or #Binding. I also tried to turn those properties into ObservableObjects, adding new classes that adopt the ObservableObject protocol, but it does not work. The only thing that seems to work is, for any type of Question, create a view, with an observable ViewModel. Later, in the view where I display all the question, I have to create a Switch with all the different possibilities.
What I don't like about this solutions is that if I wanted to add a new type of question, I would have to modify this main view to include an extra case for this new type of question, what is against the Open-Closed principle.
Do you have any suggestion guys to assign this responsibility to any question class instead of to the main view?
Thank you in advance :)
Usually you don't want a model to know about the view. That's backwards. But yet we often want to hide the concrete selection logic behind a single call that does different things depending on the data provided (similar to polymorphism via function overloading).
The key idea is the use of the Factory Pattern combined with the Visitor Pattern to keep in harmony with the Open-Closed Principle.
When we do this kind of thing with regular objects, we often use a factory method to return the proper subclass for the input data. Inside that factory method usually sits a switch statement. The factory interface lets us honor the Open-Closed principle so that ContentView doesn't change when we add new question types. Chances are, what follows is probably very similar to what you had yourself with the view model approach.
In SwiftUI, the best approach at a factory-style view would be to create a QuestionView that then knows how to create the correct concrete view for each question object. I hope that default + fatalError() makes you consider how an enum might be useful here.
struct QuestionView: View {
let question: Question
var body: some View {
switch question {
case let q as TextQuestion:
TextQuestionView(question: q)
case let q as DoubleQuestion:
DoubleQuestionView(question: q)
default:
fatalError("Unknown question type")
}
}
}
Then in your main view it would be polymorphic, reacting dynamically to the actual Question instances. You'd use it something like this:
struct ContentView: View {
#State private(set) var questions: [Question]
var body: some View {
NavigationView {
Form {
ForEach(questions, id: \.key) { question in
QuestionView(question: question)
}
Button("Submit") {
let answers = Answers()
for question in questions {
question.record(answers: answers)
}
print(answers)
}
}
.navigationTitle("Questions")
}
}
}
A reasonable way to extract the answers is to use the Visitor Pattern in a way similar in structure to Encodable's encode(to encoder: Encoder). We expect each specialty view to communicate with its specific Question object, and then expect the Question object to contain an implementation of func record(answers: Answers). When it's time, loop through questions and tell them to record their answers. (Note that we can add various Answers implementations without changing the Question subclasses, in keeping with the Open-Closed principle).
The Question objects are like view models, and they are ObservableObjects. You can see how they record their answers when asked.
For this to work, they cannot be protocols with associated types. That just kills using them in an array.
class TextQuestion: Question, ObservableObject {
#Published var answer = ""
override func record(answers: Answers) {
answers.addAnswer(key: key, value: answer)
}
}
class MeasurementQuestion: Question, ObservableObject {
let unit: String
#Published var answer = 0.0
init(key: String, question: String, unit: String) {
self.unit = unit
super.init(key: key, question: question)
}
override func record(answers: Answers) {
answers.addAnswer(key: key, value: answer)
}
}
Then each individual question subtype view will watch its own Question instance:
struct TextQuestionView: View {
#ObservedObject private(set) var question: TextQuestion
var body: some View {
Section(question.question) {
TextField("Answer", text: $question.answer)
}
}
}
struct MeasurementQuestionView: View {
#ObservedObject private(set) var question: MeasurementQuestion
var body: some View {
Section(question.question) {
HStack {
TextField("Answer", value: $question.answer, format: .number)
Text(question.unit)
}
}
}
}
You can simply add your list of questions to the preview and see how it works:
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(questions: Example.questions)
}
}
struct Example {
static let questions: [Question] = [
TextQuestion(
key: "name",
question: "What...is your name?"
),
TextQuestion(
key: "quest",
question: "What...is your quest?"
),
[
TextQuestion(
key: "assyria",
question: "What...is the capital of Assyria?"
),
TextQuestion(
key: "color",
question: "What...is your favorite colour?"
),
MeasurementQuestion(
key: "swallow",
question: "What is the air-speed velocity of an unladen swallow?",
unit: "kph"
)
].randomElement()!
]
}
I'm not sure I like this implementation for anything beyond a toy project—I prefer stronger layer separations. This does, however, setup a polymorphic view that adjusts to data implementation types. The key idea is the use of the Factory Pattern combined with the Visitor Pattern.

How can i import view which bring a closure with itself in SwiftUI? [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 6 months ago.
Improve this question
Here is my code, it does not work:
struct ContainerView<MyContent: View>: View {
let myContent: () -> MyContent
#State private var myValue: String = ""
var body: some View {
myContent() { value in
myValue = value
}
}
}
I want make this logic works, when I am bringing my view as myContent to body, I want be able to bring a string value with it like in code! I am not looking for reaching my goal with other ways, the goal of this question is be able to access value like in code as clouser.
Warning: I'm not sure what the use-case of this is -- it's not clear what problem is trying to be solved here, but there's likely a better fit that trying to make something like this work. This answer, though, does solve the compilation errors presented above.
Your syntax inside body implies that you want a trailing closure on myContent, but it's not defined in the signature. This would solve the compilation error:
struct ContainerView<MyContent: View>: View {
let myContent: (#escaping (String) -> Void) -> MyContent
#State private var myValue: String = ""
var body: some View {
Text(myValue)
myContent() { value in
myValue = value
}
}
}
Call site:
ContainerView { closure in
Button("Test") {
closure("Value")
}
}

Do I have to use an ObservableObject in SwiftUI?

I would like to use a struct instead of a class as a state for my View, and as you may know, ObservableObject is a protocol only classes can conform to.
Do I have to wrap my struct in a ViewModel or some other similar type of object ? What happens if I don't ?
A sample on what that looks like now :
import Foundation
import SwiftUI
struct Object3D {
var x : Int
var y : Int
var z : Int
var visible : Bool
}
struct NumberView<Number : Strideable> : View {
var label : String
#State var number : Number
var body : some View {
HStack {
TextField(
self.label,
value: self.$number,
formatter: NumberFormatter()
)
Stepper("", value: self.$number)
.labelsHidden()
}
}
}
struct ObjectInspector : View {
#State var object : Object3D
var body : some View {
VStack {
Form {
Toggle("Visible", isOn: $object.visible)
}
Divider()
Form {
HStack {
NumberView(label: "X:", number: object.x)
NumberView(label: "Y:", number: object.y)
NumberView(label: "Z:", number: object.z)
}
}
}
.padding()
}
}
struct ObjectInspector_Previews: PreviewProvider {
static var previews: some View {
ObjectInspector(object: Object3D(x: 0, y: 0, z: 0, visible: true))
}
}
You don't have to use #ObservedObject to ensure that updates to your model object are updating your view.
If you want to use a struct as your model object, you can use #State and your view will be updated correctly whenever your #State struct is updated.
There are lots of different property wrappers that you can use to update your SwiftUI views whenever your model object is updated. You can use both value and reference types as your model objects, however, depending on your choice, you have to use different property wrappers.
#State can only be used on value types and #State properties can only be updated from inside the view itself (hence they must be private).
#ObservedObject (and all other ...Object property wrappers, such as #EnvironmentObject and #StateObject) can only be used with classes that conform to ObservableObject. If you want to be able to update your model objects from both inside and outside your view, you must use an ObservableObject conformant type with the appropriate property wrapper, you cannot use #State in this case.
So think about what sources your model objects can be updated from (only from user input captured directly inside your View or from outside the view as well - such as from other views or from the network), whether you need value or reference semantics and make the appropriate choice for your data model accordingly.
For more information on the differences between #ObservedObject and #StateObject, see What is the difference between ObservedObject and StateObject in SwiftUI.
I would like to use a struct instead of a class as a state for my View, and as you may know, ObservableObject is a protocol only classes can conform to.
A model is usually shared among whichever parts of the app need it, so that they're all looking at the same data all the time. For that, you want a reference type (i.e. a class), so that everybody shares a single instance of the model. If you use a value type (i.e. a struct), your model will be copied each time you assign it to something. To make that work, you'd need to copy the updated info back to wherever it belongs whenever you finish updating it, and then arrange for every other part of the app that might use it to get an updated copy. It's usually a whole lot easier and safer to share one instance than to manage that sort of updating.
Do I have to wrap my struct in a ViewModel or some other similar type of object ? What happens if I don't ?
It's your code -- you can do whatever you like. ObservableObject provides a nice mechanism for communicating the fact that your model has changed to other parts of your program. It's not the only possible way to do that, but it's the way that SwiftUI does it, so if you go another route you're going to lose out on a lot of support that's built into SwiftUI.
The View is a struct already, it cannot be a class. It holds the data that SwiftUI diffs to update the actual UIViews and NSViews on screen. It uses the #State and #Binding property wrappers to make it behave like a class, i.e. so when the hierarchy of View structs is recreated they are given back their property values from last time. You can refactor groups of vars into their own testable struct and include mutating funcs.
You usually only need an ObservableObject if you are using Combine, it's part of the Combine framework.
I recommend watching Data Flow through SwiftUI WWDC 2019 for more detail.

SwiftUI MVVM pattern applied correctly? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 2 years ago.
Improve this question
I am not sure if I applied the MVVM pattern correctly to my situation.
Can you please give me some feedback if there is something wrong or can be improved?
Entry point is the App struct, which creates the Router and the PageService.
These instances are passed to all views as environment objects.
#main
struct App: App {
var router = Router()
var pageService = PageService()
var body: some Scene {
WindowGroup {
RootView()
.environmentObject(router)
.environmentObject(pageService)
}
}
}
Then inside my RootView I create a TabBarView which contains some TabBarItemViews.
The code below shows my setup (I removed the unimportant stuff and hopefully not more):
struct RootView: View {
#EnvironmentObject var pageService: PageService
var body: some View {
TabBarView(vm: TabBarViewModel(pageService: pageService))
}
}
struct TabBarView: View {
#StateObject var vm: TabBarViewModel
var body: some View {
ForEach(self.vm.tabs, id: \.id) { tab in
TabBarItemView(vm: tab)
}
}
}
class TabBarViewModel: ObservableObject {
#Published var tabs = [TabBarItemViewModel]()
var pageService: PageService
init(pageService: PageService) {
self.pageService = pageService
self.tabs = self.pageService.fetchPages()
.map(TabBarItemViewModel.init)
}
}
struct TabBarItemView: View {
#EnvironmentObject var router: Router
#StateObject var vm: TabBarItemViewModel
var body: some View {
Text(String(self.vm.id))
// using router to show selected item highlighted etc
}
}
class TabBarItemViewModel: ObservableObject {
#Published var page: Page
init(_ page: Page) {
self.page = page
}
var id: Int {
return self.page.id
}
}
Is the location of the ViewModels creation including the PageService injection correct?
Is there a way to create the TabBarViewModel inside the View and inject the PageService?
Thank you very much in advance
The views, their view (state, business logic etc) models and the app data models all appear nicely separated, so fwiw the example looks good to me - especially if the full code is working :-)
In terms of MVVM refinement; I've seen it suggested that:
Putting the view models in an extension of their view is sensible - since they are almost never reused anywhere else and it makes organisation a bit easier.
Using Protocols to specify model parameters is a good practice as it permits more flexibility, particularly with regards to the use of mock objects during testing.
For more details on the above, Kilo Loco's short YouTube video on the subject is good, it can be found here
Good luck and have fun.

LSP and SwiftUI

In order to make my code testable, I'm trying to adhere to Liskov's substitution principle by having my SwiftUI views depend on protocols rather than concrete types. This allows me to easily swap implementations and allows me to easily build mocks for testing. Here's an example of what I'm trying to do:
protocol DashboardViewModel: ObservableObject {
var orders: [Order] { get }
}
My DashboardViewModel needs to communicate changes back to its dependents, so I've also attached ObservableObject as a transitive requirement.
This seems to be a problem. You cannot achieve LSP if you have associated type requirements. Here's the error I got from my SwiftUI view class that depended on my view model:
struct DashboardView: View {
#ObservedObject var viewModel: DashboardViewModel
}
Protocol 'DatastoreProtocol' can only be used as a generic constraint because it has Self or associated type requirements
I ended up doing this instead:
protocol DashboardViewModel {
var orders: [Order] { get }
var objectWillChange: AnyPublisher<Void, Never> { get }
}
This also requires dependents to do additional work to observe for state changes. This takes away the conveniences of using property wrappers - mainly the ability for dependents to observe for state changes using #ObservedObject. Using this alternative leads us to code like this:
struct DashboardView: View {
let viewModel: DashboardViewModel
var viewModelSubscriber: AnyCancellable!
// MARK: - Used only to force a re-render of this view
#State private var reload = false
init(viewModel: DashboardViewModel) {
self.viewModel = viewModel
viewModelSubscriber = viewModel.objectWillChange.sink { _ in
self.reload.toggle()
}
}
}
This is a tad distasteful to look at:
Created a #State variable that is only used to force an update of the view, because we cannot utilize SwiftUI property wrappers to observe for state changes
Created a AnyCancellable! variable to hold a subscription to objectWillChange from the view model. This is necessary to detect state changes from the DashboardViewModel
Added the subscription call in the initializer that only toggles the #State variable to force a retrieval of new data from the view model
I feel like there should be a much better way of handling this. Looking for help!
One way to solve this is to make your view generic:
protocol DashboardViewModel: ObservableObject {
var orders: [Order] { get }
}
struct DashboardView<Model: DashboardViewModel>: View {
#ObservedObject var viewModel: Model
}