I have a silent remote notification coming in on a SwiftUI app. It is not picked up by the UNUserNotificationCenter, but rather, by the old AppDelegate didReceiveNotification func. What is the solution for notifying ContentView() that a change has occurred? An ObservableObject in AppDelegate?
You can declare a store which conforms ObservableObject in the AppDelegate and set it as an environment object to the ContentView.
// AppDelegate.swift
// in the class
let store = Store()
// in the applicationDidFinishLaunching(_:) method
window.contentView = NSHostingView(rootView: contentView.environmentObject(store))
// ContentView.swift
// in the struct
#EnvironmentObject var store: Store
The environment object is a reference so you can pass the value into it. Same to using ObservableObject. When you finish updating just call objectWillChange.send() or mark the property to #Published. And ContentView will be updated after notified.
Related
I have a banner component written in SwiftUI that I am embedding in a ViewController using a UIHostingController.view as a return type in a method:
public static func createBannerView(thing: String) -> UIView? {
let viewModel = bannerViewModel(dependency: SomeRepository(thing: thing))
return UIHostingController(rootView: bannerView(viewModel: viewModel)).view
}
//I do check for nil, but shortened for readability...
infoBanner.addSubview(createBannerView())
NSLayoutConstraint.activate([...
Inside the view, I am referencing the ViewModel as an #ObservedObject.
I have heard concerns that I should be referencing the ViewModel as a #StateObject instead, because the ViewModel is being created outside of the ViewController. I understand this concern in a situation where the parent view and child view are SwiftUI views, because changes to the immutable parent view will recreate the child view and without the #StateObject declaration the data in the #ObservableObject will be lost.
But in this situation, the parent UIKit powered ViewController is not immutable, and can change all it wants without forcing a redraw of the child SwiftUI view.
My questions:
1: Is there a benefit in this situation to marking the ViewModel as a #StateObject or #ObservedObject
2: Is there a danger to marking the ViewModel as a #StateObject or #ObservedObject
UPDATE
An answer on another thread gives a great explanation of how these two objects differ: #StateObject vs #ObservedObject when passed externally but owned by the view
Sorry I am new in Swift, I have a curious question about global sharing ObservableObject
I saw this answer and understand #EnvironmentObject here
https://stackoverflow.com/a/59919052/9585130
Instead using #EnvironmentObject why we don't use like this?
class Person: ObservableObject {
let shared = Person()
}
Then anywhere in view we can use like below and no need EnvironmentObject any more?
struct BookList: View {
#ObservedObject var persionViewModel = Person.shared
...
}
struct BookView: View {
#ObservedObject var persionViewModel = Person.shared
...
}
Is it impact to memory leak issue?
Using #EnvironmentObject injects this dependency into a view and it’s subviews.
If at some point the root view (the one the dependency was injected into) gets removed, the environment object will (should) also be deallocated.
So #EnvironmentObject storage is a bit like a service registry rather than a singleton.
I have code like this:
struct MenuView : View {
#Environment(\.verticalSizeClass) var sizeClass
#EnvironmentObject var model : MenuModel
#ObservedObject var items = MenuItems()
var body: some View {
}
}
And I consider why ObservableObject is not keeping it state (there is no one instance of this object) but rather it is recreated (init() method is called) on each View redrawing while some other state changes. I think it is once per object.
But tapping the button causes View to be recreated and also this ObservedObject to be recreated.
Should I create it not via property initialization but rather in parent and pass it to constructor?
How to correctly pass #ObservedObject to child view? I consider passing it to #Bindable property but it doesn't work.
I am changing via Button model.isMenuOpen #Published property
There is some "magic" by SwiftUI in restoring the state (be it #State or #ObservedObject) of a view. Those states are managed by SwiftUI, and they are restored before body is called.
Your child view can have an initialization, but note it has to be like this:
init(foo: Foo) {
self._foo = ObservedObject(initialValue: foo)
}
You you might also want your view to extend Equatable, to help in the diff-ing.
I wrote more on the weird things about state here: https://samwize.com/2020/04/30/a-few-things-about-state/
The ember way:
According to ember's documentation about views' eventManagers, they must be created in the parent classes definition like so:
AView = Ember.View.extend({
eventManager: Ember.Object.create({
which encapsulates and isolates them from their parent view (AView).
The only way of accessing the context of events is through the view parameter that gets passed in along with each event
dragEnter: function(event, view) {
My situation:
I'm doing a lot of work with the various drag events inside a large view with many subviews, inputs, checkboxes, etc.
Following this form, my code is beginning to go to great lengths to determine which sub-view each event originated from, and then taking different paths to access the common parent controller:
drop: function(event, view) {
var myController;
if(view.$().hasClass('is-selected') ||
view.$().hasClass('list-map-container')) {
myController = view.get('controller.controllers.myController');
} else if(view.$().hasClass('ember-text-field')) {
myController = view.get('parentView.parentView.controller');
} else {
myController = view.get('controller');
}
// do work with myController
}
My hack:
In order to simplify I used the didInsertElement hook in the parent view to assign the desired controller as a property on the eventManager:
App.MyView = Ember.View.extend({
didInsertElement: function() {
this.set('eventManager.controller', this.get('controller'));
},
eventManager: Ember.Object.create({
controller: null,
// ...
This works to significantly simplify my event handlers:
drop: function(event, view) {
var myController = this.get('controller');
// do work with myController
My question:
My intuition tells me this hack-around isn't the best solution.
Perhaps I shouldn't be doing all the work in the eventManager? Rather move all this work to a controller and just forward the events from the view?
But if the eventManager is an acceptable workspace, then what is the best way to access the parent view's controller?
I know this is a late answer but this SO question appears as a result of google. Here is how I did this when searching through emberjs examples.
To access the view within the eventManager, you have to specify two argument in the event function handler :
eventManager: Ember.Object.create({
keyUp: function(event, view){
view = view.get('parentView'); // The view parameter might not be the current view but the emberjs internal input view.
view.get('controller'); // <-- controller
}
}),
Correct me if I'm wrong, but it looks like all the controller logic is encapsulated to a text-field--if so, I think a component might better suited for this use case. It's essentially a controller and view as one, and the eventManager's callbacks' view parameter gives you control over the component/controller itself.
If you need access to the component's parent controller, you might want to bind to events on the component from the parent controller, because the component really shouldn't know about anything outside its scope.
In previous versions of Ember, the javacsript event object would be passed as a parameter to the event handling function of the route. In RC1 it is no longer passed. I also can not find any way to get the view object from the route. I am build a form to upload a file to the backend datastore using the FormData object. I know I could handle the event within the view rather than the route, but I would prefer to handle all events that change router state in the router itself.
This code worked in previous versions:
add_asset: Ember.Route.extend({
upload: function(router, event) {
var form = event.target.form;
var view = event.view;
var form_data = new FormData(form);
var uploadModel = new App.Asset();
var success_callback = function(){
console.log('uploaded!');
router.transitionTo('root.portfolios.show_portfolio', event.context)
};
var error_callback = function() {
console.log('error uploading');
};
uploadModel.upload(form_data, success_callback, error_callback);
}
});
I am unable to find a way to get either the event object or view object from with the route event handler. Is this possible? Thanks.