I'm trying to update the view of an item belonging to a CollectionView.
The best way should call "render()" from that particular view?
Despite the model change
I think I'm missing something important here, because, although the model has changed, calling "render()", does not update the view.
Thanks for your help!
Regards!
The best way to call "render()" is to subscribe CollectionView to some event for example "update_collection_view". Read more on Chaplin.mediator. This is common chaplin mechanism to communicate and exchange information, this prevent memory leaks. Often event raises in "fetch()" method of collection or model after loading data from server.
define [
'chaplin'
'views/base/view'
'views/base/collection_view'
], (Chaplin, View, CollectionView) ->
class SomeView extends View
template: template1
template = null
foo: ->
#here we update collection view
Chaplin.mediator.publish 'update_collection_view'
class SomeCollectionView extends CollectionView
itemView: SomeView
template: template2
template = null
initialize: ->
super
#subscribeEvent 'update_collection_view', #render
Related
I'm working on a SwiftUI app that is to display a number of entries from a Core Data database. Basically just about what the XCode template for such apps implements.
I've made two changes:
I put the code for the list item view into a separate file (BookmarkableItemView)
I added a property bookmarked to the item, which I called BookmarkableItem
The code in my view to show the list of items is now:
List(bookmarkableItems) { item in
BookmarkableItemView(item: item)
}
In BookmarkableItemView, the item property is an #ObservedObject.
Now, my BookmarkableItemView also contains a button that should toggle the bookmarked property on the item that the view represents. All fine and well - this is easy. But now I have to persist the changes...
I can think of the following two solutions:
Pass an action (simple a (BookmarkableItem) -> Void) to the BookmarkableItemView that's then called when button in the view is pressed
Have the view itself save the managed object context
The latter is a no-go in my opinion. The first solution works. There's now an bookmarkAction property in the BookmarkableItemView, so the above code looks like that:
List(bookmarkableItems) { item in
BookmarkableItemView(bookmarkAction: { toggleBookmark(item) }, item: item)
}
The action is called by the BookmarkableItemView and as a consequence, the toggleBookmark(_ item:) method is called.
But somehow I feel that I'm missing something about bindings in SwiftUI that would make the process easier? How can I have my parent view get a notification when a sub view performs changes to the data model so I can persist them?
onDisappear check if item.hasChanges
or use onChange on item.hasChanges
Since Xcode 12.5, I am seeing a lot of "Unable to present. Please file a bug." console logs in the Xcode console which I remember not seeing prior to 12.5.
This message is shown when I am using NavigationLink from a parent view to navigate to a child view, and if logic in the child view updates one of the states that parent view depends on.
Below is a sample pseudo code where a list of messages are shown in the parent view, message detail is shown in the child view from a list, and lastly, independent settings child view.
struct MessageListView: View {
...
#StateObject var messageList = MessageList()
var body: some View {
debugPrint("!!MessageListView has been redrawn!!")
return VStack {
NavigationLink(destination:SettingsView()){
Text("Go to settings")
}
ForEach(messageList.data.sorted(by: {$0.key < $1.key}), id:\.key) { k, m in
NavigationLink(destination:MessageView(message:...){
Text(m.text)
}
}
}
}
}
struct MessageView: View {
...
#ObservedObject var messageList : MessageList
#ObservedObject var message : Message
...
var body: some View {
Text(...)
.onAppear {
messageList.readAndIncrement(message.id) //<- This updates both this view & parent view.
}
}
}
class MessageList : ObservableObject {
#Published var data : [String:Message] = [:]
func readAndIncrement(id: String){
//Modify one of the message in dictionary.
}
}
So when user clicks on message and traverses the navigation like below,
MessageListView -> MessageView
As soon as MessageView appears on screen, it will increment message's "read count" due to at the logic in onAppear, which will update data in MessageListView at the same time.
As soon as that happens, it appears that the parent view, MessageListView which is observing MessageList objects gets updated, the two following things happen.
debugPrint will print message !!MessageListView has been redrawn!! (from source code above) on console, which proves that the parent view has been updated while Navigation is currently showing child view.
'Unable to present. Please file a bug.' console log gets shown on screen, probably because the update in the parent view "cannot" be presented on screen?
So SwiftUI seems to be throwing "Unable to present" error log when I am updating parent view's observed data while viewing & interacting in the child view, but I am unsure how I can properly fix to get rid of this error.
The reason that I am thinking this is not a bug, but my error is because of the following.
When user traverses into a completely different view, something like
MessageListView -> SettingsView
and if the following two conditions are met,
The child view (SettingsView) does not use or rely on any of the parent view's state objects.
Parent view's data model is updated by other means, such as from network updates/sync, periodic refresh, etc.
then the parent view (MessageListView) gets redrawn for its dependent model's every single update and the child view starts stacking itself like below.
At least, in the above case, Unable to present. Please file a bug. doesn't get shown in the console.
In the iOS 14.5 & Xcode 12.5 patch notes, the only comments related to NavigationLink is as follows, but that does not seem to be relevant to my case.
The destination of NavigationLink that only differs by local state now resets that state when switching between links as expected. (72117345)
So my question is... how can I properly manage states (or decouple them) in this case that won't cause funkiness in SwiftUI Navigation?
Is it against SwiftUI's paradigm to update parent view's state data while presenting child view on screen or is this simply a NavigationLink bug in Xcode 12.5?
I am answering my own question about the workaround that I found. The solution that worked for me is to use Class instead of Struct and update properties of the Class. This prevented the View, which is watching the #Published array/list, from getting updated.
Previously, I was using Struct for the Message type. What ended up happening is that any change to one of the properties of the struct/Message will trigger UI updates (which is expected), even if View is not directly watching the struct itself, but watching the list of structs (MessageList class's data property in my case.).
I converted Message from Struct to Class that implements ObservableObject and updates its properties. That prevented parent View, which dependent on the list of Messages from getting redrawn, when one of the list elements had to be updated.
In summary, when you assign #Published annotation to an array of Struct, list item addition/removal as well as any change to one of the properties of the struct item in the list will trigger dependent View updates in SwiftUI.
On the other hand, if you assign #Published annotation to an array of Class, only list item addition/removal will trigger SwiftUI update. Any change to one of the properties of the Class won't trigger dependent View updates in SwiftUI.
I have 2 view A and B with their respective controllers and models defined inside. Due to some reason I have to move 2 elements from A to B view. One of the element is a button link with a press event. I moved the elements to another view but when I click on the link, it does not trigger press. Which I understand is because B 's controller does not have function for that. If I write the same function in B's controller I get an error saying the model's setProperty could not be set as not found. Because that model is in scope in A's controller only. How do I access it?
Press event of link:
pressEvent: function(oEvent) {
this.getModel("stock").setProperty("/Links/Visible", true);
var stockroomsarr = this.getModel("stockRms").getProperty("/Stockrooms");
if (stockrooms !== null && stockrooms.length > 0) {
this.getModel("stock").setProperty("/Text/Visible", false);
}
this.getModel("stock").refresh(false);
},
Regarding your Model: I strongly advice not to have own models for each controllers. Instead you should define and set them in the init of your component.js. They then are accessable in the view controller by calling
this.getOwnerComponent().getModel("urModel")
Another variant would be to create a baseController. In that you can define functions which are to be used by multiple controllers as well as your models. All other controllers extend that baseController, so the models are available in all of them via:
this.getView().getModel("urModel")
Regarding the function you may use the Event Bus. In short it allows you to create events that are triggered in controller A and are listned to in controller B. Read up here for detailed information on how to use it:
https://blogs.sap.com/2015/10/25/openui5-sapui5-communication-between-controllers-using-publish-and-subscribe-from-eventbus/
I am just wondering how can I get one of the UI5 views in an application.
I know there is a method:
sap.ui.jsview(); // in case the view is written in JavaScript
But the problem with this method is: if you assign ID for any of the controls and you have already inflated this view, you get an error.
So I want to know how to check if the view already exists and then if yes return that existing view, otherwise create the view with the corresponding API such as the above one.
I also know in the control for view I can go
this.getView();
But as I said, how to get this view from another view?
I am not quite understanding your question
With managed object id's are unique, so if you try and create the same view twice you will get an error.
when you create your view the easiest way to access it is via an Id
sap.ui.jsview("view1",'testapp.view.view1');
sap.ui.getCore().byId('view1');
NB. views should not talk to anyone other than their controller A terrific Model View Controller (MVC) diagram
sap.ui.getCore().byId(<id_of_the_view>)
Where <id_of_the_view> can be obtained in the following way:
suppose that the corresponding controller of <id_of_the_view> is "controllerA.js",
then you can console.log, inside controllerA.js,
console.log(this.getView())
This will print you an object which contains the id of such view. This will be <id_of_the_view>
I think here is one solution.
Make a global variable;
Use it to create element.
In First View:
var mytextField ;(use it as global)
mytextField = new sap.ui.commons.TextField('textfieldId');
In Second View:
var myValue = mytextField .getValue();
~Mansi Rao
I was wondering what your thoughts are on having a ViewModel containing a collection of other ViewModels.
For example if i have a stock price screen. In the MainView i want to be able to selct a stock ticker. When i press the ADD button on the MainView it should display a new stock price in the MainView.
My question is about how the add button should work?
Which of the two options should the Add button on the MainView do:
1) Pass the stock ticker (MSFT) to StockPriceService. The StockPriceService will retrive a StockPrice object. I can then pass the StockPrice object into a StockPriceViewModel. The MainViewModel will contain a collection of StockPriceViewModel.
2) pass the stock ticker(MSFT) into the StockPriceViewModel. The StockPriceViewModel will be implemented to call the StockPriceService and retrive the StockPrice object. The the StockPrice object will then be wrapped in the StockPriceViewModel.
Thanks,
CA
If the Add button is part of the MainView then it's events really should be handled by the MainViewModel and access to data for the StockPriceViewModel and thus StockPrice object, by the StockPrice related code.
The way to look at this is that you should still be able to display a StockPriceViewModel with populated data without recourse to any code in any other ViewModels, but in this case it is the MainViewModel that triggers the creation/instantiation of a new StockPriceViewModel etc.
So that would mean go with mostly method 2, say passing an initialisation value to the StockPriceViewModel constructor.