SwiftUI Change Observed Object From Another View - ios

I have an observed object in my ContentView which contains tabviews.
class GlobalValue: ObservableObject {
#Published var stringValue = ""
#Published var intValue = 0
}
struct ContentView: View {
#ObservedObject var globalValue = GlobalValue()
}
From one of the tabs I need to change ContentView's observed object value.
ContentView().globalValue.intValue=25
It is not changing the value. How can I change that observed object value? thanks...

It is changing the value. The problem is you are instantiating an instance of GlobalValue locally in your struct. It has no life outside of the struct. I you want to use an Observable Object as a global store like that, you need to create it once and use that instance.
The easiest way to do this is to add
static let shared = GlobalValue() in your class, and in your struct use globalValue = GlobalValue.shared which essentially gives you a singleton. You will have one instance that all the views can read and write to.

Related

Passing data to subview with Core Data and MVVM

I am using SwiftUI and Core Data with MVVM.
I have a ForEach loop and I want to pass the data to the subview. First I did this using a property like this:
#StateObject var viewModel = ListViewModel()
ForEach(viewModel.items) { item in
NavigationLink {
ItemDetailView() // empty view
} label: {
ItemListRowView(name: item.name!)
}
}
Then in the subview ListRowView would be something like:
let name: String
Text("\(name)")
And the view model where the ForEach is grabbing its data:
#Published var items: [ItemEntity] = []
#Published var name: String = ""
func getItems() {
let request = NSFetchRequest<ItemEntity>(entityName: "ItemEntity")
do {
items = try dataManager.context.fetch(request)
} catch let error {
print("\(error.localizedDescription)")
}
}
That works as expected but now I want to edit the data and pass more properties to the subviews. I think this means I need to use bindings and #ObservedObject in the subviews.
What I see commonly done is one would make a custom Item data type conforming to Identifiable protocol, for example:
struct Item: Identifiable {
let id: UUID
let name: String
}
And then they'd update their ForEach to use the Item type and do something like let items: [Item] = [] but I've already got let items: [ItemEntity] = [] with ItemEntity being the name of the Core Data Item entity.
What I suspect needs to happen is in my getItems method, items needs to be changed to use an Item data type. Is this correct? Or how should I go about this? I'm shiny new to Core Data and MVVM and any input will be super appreciated.
Edit: I've seen this done too but I'm not sure if it's what I'm looking for:
ForEach(viewModel.items.indicies) { index in
SubView(viewModel.items[index])
}
Couple of mistakes:
ForEach is a View, not a loop, if you attempt to use it with indices it will crash when you access an array by index in its closure. In the case of value types you need to supply the ForEach with an id which needs to be a property of the data that is a unique identifier. Or the data can implement Identifiable. However, in the case of objects like in Core Data, it will automatically use the object's pointer as its id, which works because the managed object context ensures there is only one instance of an object that represents a record. So what this all means is you can use ForEach with the array of objects.
We don't need MVVM in SwiftUI because the View struct is already the view model and the property wrappers make it behave like a view model object. Using #StateObject to implement a view model will cause some major issues because state is designed to be a source of truth whereas a traditional view model object is not. #StateObject is designed for when you need a reference type in an #State source of truth, i.e. doing something async with a lifetime you want to associate with something on screen.
The property wrapper for Core Data is #FetchRequest or #SectionedFetchRequest. If you create an app project in Xcode with core data checked the template will demonstrate how to use it.

Difference between CurrentValueSubject and #Published

So I'm digging into combine and this question came up.
Is there any real difference between using CurrentValueSubject (and setting its value using currentValueSubject.value) or using a #Published var and accessing its publisher with a $? I mean I know one returns a Subject instead of a Publisher, but the only real difference I could find is that CurrentValueSubject is way more useful because you can declare it on a protocol.
I really don't understand how #Published can be useful if we can just use PassthroughSubject, am I missing something here?
Mind you, this is using UIKit, it may have other uses for SwiftUI.
Thank you.
CurrentValueSubject is a value, a publisher and a subscriber all in one.
Sadly it doesn’t fire objectWillChange.send() when used inside an ObservableObject.
You can specify an error type.
#Published is a property wrapper, thus:
It is not yet supported in top-level code.
It is not supported in a protocol declaration.
It can only be used within a class.
#Published automatically fires objectWillChange.send() when used inside an ObservableObject.
Xcode will emit a warning if your try to publish to #Published wrapped property from a background queue. Probably because objectWillChange.send() must be called from the main thread.
The error type of its publisher is Never
My biggest beef against #Published is that it can’t behave as a subscriber and setting up Combine pipelines requires additional plumbing compared to a Current Value Subject.
We can declare a #Published property inside a protocol. Not very pretty...
protocol TestProtocol {
var isEnabled: Bool { get }
var isEnabledPublished: Published<Bool> { get }
var isEnabledPublisher: Published<Bool>.Publisher { get }
}
class Test: ObservableObject, TestProtocol {
#Published var isEnabled: Bool = false
var isEnabledPublished: Published<Bool> { _isEnabled }
var isEnabledPublisher: Published<Bool>.Publisher { $isEnabled }
}
#Published is just a quick way to use CurrentValueSubject a little neater. When I debug one of my apps and look at the type returned by $paramName , it's actually just a CurrentValueSubject:
po self.$books
▿ Publisher
▿ subject : <CurrentValueSubject<Array<Book>, Never>: 0x6000034b8910>
I guess one benefit of using CurrentValueSubject instead of #Published may be to allow you to use the error type?
Note: Despite being a CurrentValueSubject right now I'd never rely on that assumption.
I found myself coming back to this post so felt I'd add some extra insight in to the difference between #Published and CurrentValueSubject.
One main difference can be found in the documentation for #Published:
When the property changes, publishing occurs in the property’s willSet block, meaning subscribers receive the new value before it’s actually set on the property.
Additionally, conversation on the Swift Forums note that #Published is intended for use with SwiftUI.
With regards to #Published publishing in the willSet block of it's property, consider the following example:
class PublishedModel {
#Published var number: Int = 0
}
let pModel = PublishedModel()
pModel.$number.sink { number in
print("Closure: \(number)")
print("Object: \(pModel.number) [read via closure]")
}
pModel.number = 1
print("Object: \(pModel.number) [read after assignment]")
This produces the following output:
Closure: 0
Object: 0 [read via closure]
Closure: 1
Object: 0 [read via closure]
Object: 1 [read after assignment]
Contrast this with another example where we keep everything the same, except replacing #Published with CurrentValueSubject:
class CurrentValueSubjectModel {
var number: CurrentValueSubject<Int, Never> = .init(0)
}
let cvsModel = CurrentValueSubjectModel()
cvsModel.number.sink { number in
print("Closure: \(number)")
print("Object: \(cvsModel.number.value) [read via closure]")
}
cvsModel.number.send(1)
print("Object: \(cvsModel.number.value) [read after assignment]")
Output:
Closure: 0
Object: 0 [read via closure]
Closure: 1
Object: 1 [read via closure] // <— Here is the difference
Object: 1 [read after assignment]
After updating number to 1, reading the object's CurrentValueSubject's value property within the closure prints the new value instead of the old value as with #Published.
In summary, use #Published within your ObservableObjects for your SwiftUI views. If you're looking to create some sort of model object with an instance property that holds a current value and also publishes it's changes after they are set, use CurrentValueSubject.
One advantage on #Published is that it can act as a private-mutable, public-immutable CurrrentValueSubject.
Compare:
#Published private(set) var text = "someText"
with:
let text = CurrentValueSubject<String, Never>("someText")
When designing APIs you often want to allow clients to read the current value and subscribe to updates but prevent them from setting values directly.
There is one limitation when using #Published.
You can only use #Published on properties of a Class whereas CurrentValueSubject can be used for struct as well

How can I unwrap an optional value inside a binding in Swift?

I'm building an app using SwiftUI and would like a way to convert a Binding<Value?> to a Binding<Value>.
In my app I have an AvatarView which knows how to render an image for a particular user.
struct AvatarView: View {
#Binding var userData: UserData
...
}
My app holds a ContentView that owns two bindings: a dictionary of users by id, and the id of the user whose avatar we should be showing.
struct ContentView: View {
#State var userById: Dictionary<Int, UserData>
#State var activeUserId: Int
var body: some View {
AvatarView(userData: $userById[activeUserId])
}
}
Problem: the above code doesn't combine because $userById[activeUserId] is of type Binding<UserData?> and AvatarView takes in a Binding<UserData>.
Things I tried...
$userById[activeUserId]! doesn't work because it's trying to unwrap a Binding<UserData?>. You can only unwrap an Optional, not a Binding<Optional>.
$(userById[activeUserId]!) doesn't work for reasons that I don't yet understand, but I think something about $ is resolved at compile time so you can't seem to prefix arbitrary expressions with $.
You can use this initialiser, which seems to handle this exact case - converting Binding<T?> to Binding<T>?:
var body: some View {
AvatarView(userData: Binding($userById[activeUserId])!)
}
I have used ! to force unwrap, just like in your attempts, but you could unwrap the nil however you want. The expression Binding($userById[activeUserId]) is of type Binding<UserData>?.

It seems like each View Controller is creating a unique instance of my struct - which I don't want?

I'm creating an app in Swift 2.0 xCode7 using the Tabbed-Application template, with each screen having a separate ViewController. I have a struct to manage a variable I want to be accessed by all view controllers. I created the instance of the struct in the first view controller. I'm able to access the struct data and methods in the other views, but if update the data in one view, it doesn't change for all... It's acting as if each View Controller is creating its own instance on its own. I don't want that. I want each ViewController to share the same updated data in the struct. Does this mean that I should be creating a Singleton Pattern? Or, something else? I'm quite new at this, so thanks for your patience.
I'm not sure how exactly you access the structure but it might be that you only need to change struct to class because structs are value types so if you assign it or pass into a method it is copied whereas an instance of a class will avoid copying
Because you didn't give me any code, this is just my guess.
Structs are different from classes. The former stores values and the latter stores references. Let's look at this code:
var obj = SomethingCool()
obj.somethingCooler = 20
var obj2 = obj
obj2.somethingCooler = 10
If SomethingCool were a struct, obj.somethingCooler would still be 20 but obj2.somethingCooler would be 10. On the other hand, if SomethingCool were a class, both obj.somethingCooler and obj2.somethingCooler would be 20.
This is because the third line. The third line is VERY important. If SomethingCool were a struct, the values stored in obj will be copied to obj2. i.e. Two set of independent values would be created. If it were a class, the object that obj will also be referenced by obj2. i.e. There would still be just one object.
Now that you know the difference, I can tell you that you must have done something like the third line in your view controllers, haven't you?
To solve this problem, you can change from a struct to a class. Or you can create something like this:
public class SomeName {
static var myData: SomeTypeOfStruct {
return something
}
}
If you are so hellbent on keeping it as a struct you could do something that swift actually helps u out with.....AppDelegate!
The appdelegate.swift is a single instance object for any application. So in case you want to save a value that you need to access throughout the application or update throughtout the application, you might want to use AppDelegate.
E.g.
In FirstViewController.swift set the AppDelegate variable that you want to reflect on the remaining screens:
(UIApplication.sharedApplication().delegate as! AppDelegate).commonVariableName = NewValueYouWant;
In the SecondViewController.swift, take up that value from the AppDelegate
var updatedValue = (UIApplication.sharedApplication().delegate as! AppDelegate).commonVariableName;
Again...as #Sweeper said, you can always switch to class which is more reliable and used to achieve something like this.
It's acting as if each View Controller is creating its own instance on
its own.
It's all explained in Apple's Swift guide:
Structs:
struct Dog {
var name: String
}
var d1 = Dog(name: "Rover")
var d2 = d1
d2.name = "Sally"
print(d1.name)
print(d2.name)
--output:--
Rover
Sally
Classes:
class Cat {
var name: String = ""
}
var c1 = Cat()
c1.name = "Kitty"
var c2 = c1
c2.name = "Gerald"
print(c1.name)
print(c2.name)
--output:--
Gerald
Gerald
See the difference?

How is 'let' implemented?

I pulled this example straight from this Apple page
struct FixedLengthRange {
var firstValue: Int
let length: Int
}
and if you assign an instance of this structure to a constant,
let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
it says we can't change its property values even if it is declared as 'var'
This makes me wonder how let is implemented? I hope any assignments to it can be detected at compile time and show compile error. But in the above case, why does it apply to every property of the structure, regardless of how it is defined?
I tried to search for this, finding it very difficult to search with keyword 'let' as it is quite common term.
Can anyone help me to understand this?
It's because a struct is a value type. This means it cannot be mutated in place.
Thus, if we have a variable rangeOfFourItems that is a FixedLengthRange struct instance, and we want to set rangeOfFourItems.firstValue, we are actually ripping the struct instance right out of the rangeOfFourItems storage and replacing it with another struct instance with a different firstValue.
To see that this is true, declare rangeOfFourItems with var and attach a setter observer to it, and then change rangeOfFourItems.firstValue:
struct FixedLengthRange {
var firstValue: Int
let length: Int
}
var rangeOfFourItems = FixedLengthRange(firstValue:1, length:4) {
didSet {
print("Hey, you set me!")
}
}
rangeOfFourItems.firstValue = 2 // Hey, you set me!
This shows that merely setting the property of this struct instance actually sets the struct variable itself.
But in your code, we cannot do that, because rangeOfFourItems prevents this implicit assignment - it is declared with let, meaning it must remain a constant. Thus, setting rangeOfFourItems.firstValue is prevented at compiler level.
(If FixedLengthRange were a class instead of a struct, it would be a reference type, and would be mutable in place, and setting rangeOfFourItems.firstValue would be legal even if rangeOfFourItems was declared with let.)

Resources