CellMeasurer with function child ({ measure }) => <div />) renders excessive number of times - react-virtualized

REF: demo at https://codesandbox.io/s/1vy7ljo877
The demo uses 2 approaches to render some images.
The first row renders an element inside CellMeasurer
while the second row renders a function with signature: ({ measure }) => )
I find that for the function render, while the size is calculated correctly, the render method gets called n^2 # of times where n is approximately the number of items that it tries to display.
If deferredMeasurementCache is turned on, the problem gets exponentially worse since n becomes the size of the collection.
The above results in crazy long page load times and very sluggish behavior when scrolling.
Is this normal behavior or am I implementing the function inside CellMeasurer incorrectly?

If deferredMeasurementCache is turned on, the problem gets exponentially worse since n becomes the size of the collection.
If you're not passing the CellMeasurerCache to Grid as a deferredMeasurementCache prop, then Grid isn't guaranteed to work correctly. It's necessary for it to know about the cache for a couple of cases. (Maybe check out this part of the docs to make sure you aren't misunderstanding how CellMeasurer is supposed to work when used to calculate the width/height of a row/column.)
That being said, what you're describing sounds like a bug, but I'm not sure it actually is. At a high level, it's caused by the CellMeasurer measure method calling Grid.recomputeGridSize() which synchronously triggers forceUpdate() (causing all visible cells to re-render).
At first I thought the solution might be to just debounce or queue a setState action instead. Unfortunately that only pushes the problem away a little; the possibility still exists. I can't debounce for too long, or I risk the Grid showing cells to the user that are clearly the wrong size. (A debounce that's too big might even allow a user to keep scrolling ahead of it, outrunning the re-render.) It's also possible that images will load far apart (after a delay) in a real app, defeating any debounce I might do anyway.
The problem here is that any time a cell reports it has a different size, the Grid needs to relayout the other cells (potentially shifting them up or down). In this simple example it seems unnecessary- because all images end up having a uniform width and height. This isn't typical though. Since you've configured your CellMeasurerCache to measure height, and since the height of each column impacts the height of the row (and thus all other columns, as explained in the docs I linked to above), Grid has to re-render all cells each time one reports a change.
Put another way, if you added more rows, and changed your example to be more like this:
const makeImages = (count=10, startIndex=0) => {
const width = 100;
const imagesArray = _.times(count, (index) => {
const height = 100 + Math.round(Math.random() * 200);
return {
key: startIndex+index,
src: `http://placehold.it/${width}x${height}/${((1<<24)*Math.random()|0).toString(16)}/fff?text=${startIndex+index}`,
}
});
return imagesArray;
};
Then maybe it's easier to see why a change to a cell in row 1, column 1 could potential impact the height of all cells in row 1, and the position of all cells in the grid.

Related

How to avoid usage of Task.Delay while scrolling to a particular point in Scroll View as I am adding children in runtime which are out of view?

Here, I have used a scroll view and children are added upto visible height and the other children are added in runtime while scrolling down. Hence I need to scrollTo a particular point like Scrollview.ScrollTo(x,y) for this I need to calculate y by adding children height.Height is calculated by using delay to bring the view in screen first and I calculate. But need to calculate once the children displayed while scrolling without delay
ScrollViewer.ScrollTo(0, (int)totalheight);
await Task.Delay(500);
totalheight += this.CalculateHeight(ScrollView.GetChildrenAt(i));
Where children view height is return in the last method, but without delay the view become null.

How Can I Debug React-Virtualized's InfiniteScroll/Grid Fetching?

I've got a React Virtualized InfiniteList/Grid combination, following the basic pattern from the documentation (https://github.com/bvaughn/react-virtualized/blob/master/docs/InfiniteLoader.md#infiniteloader-and-grid).
It works great, except for two things:
If I scroll too fast, instead of waiting for things to catch up, it resets the scroll to the top of the grid
The combo only fetches the first N results (eg. 125) ... even though I have 859 results (and even though I have provided 859 as the rowCount prop)
This is especially strange because it fetches in increments of 25, so this means that everything works fine the first 5 times and then inexplicably fails on the 6th.
I've tried everything I can to figure out what's going on, and even when I fill the React Virtualized code with console.log and debugger statements I still can't figure out why it stops at 125 results (or why fast scrolls reset).
Can anyone more familiar with React Virtualized point me to the spot in the code where it decides whether to keep fetching or stop (and possibly reset the start index to 0)? I can tell that the InfiniteScroll's onRowsRendered and scanForUnloadedRanges and Grid'sonSectionRendered are involved, but I still can't figure out where the value processing stops and the actual "decide whether to keep going" logic begins.
As #brianvaughn's suggested in his comment, the InfinititeLoader component (https://github.com/bvaughn/react-virtualized/blob/master/source/InfiniteLoader/InfiniteLoader.js) is partly responsible. Whenever rows are rendered _loadUnloadedRanges method gets called from scanForUnloadedRanges, and it handles loading the ranges that scanForUnloadedRanges found.
However, that's only part of the equation. The parameters that are passed in to onRowsRendered come from Grid. The Grid _updateScrollTopForScrollToRow method gets called as the user scrolls, and that method has the following line:
const targetIndex = Math.max(0, Math.min(rowCount - 1, scrollToRow))
That targetIndex will ultimately affect which rows get loaded, and as you can see it depends on the rowCount that is passed in initially.
This is what caused problems for me. I started with an InfiniteLoader + List combination, and in that setup a "row" means the same thing to both components. However I then switched to a InfiniteLoader + Grid combination, and to a Grid a "row" is an array of InfiniteLoader rows. In other words, a Grid "row" is a row of cells, but an InfiniteLoader "row" is a cell.
My method for calculating the rowCount was changed to use the Grid definition of a "row", but since it was being passed to InfinniteLoader I should have used InfiniteLoader's definition of a "row" instead. In other words, I should have returned just numItems, not numItems/columnCount.
All this could have easily been avoided if the React Virtualized used an "items" prop for InfiniteLoader instead of using a name that's also used (with two different meanings) by two other React Virtualized components ... but at least now I have a much better understanding of how these components work.

Particles generated behind sprite

I just started out with Phaser.
I have a simple sprite in the middle of the screen, and whenever I click the sprite, I emit a particle at the clicked x,y coordinates.
My problem is that the particles are generated behind the sprite. I have tried setting z on the sprite to 1 and the emitter to 1000 without luck.
What am I missing?
var emitter = game.add.emitter(game.world.centerX, game.world.centeryY);
emitter.makeParticles('phaser');
var sprite = game.add.sprite(game.world.centerX, game.world.centerY, 'phaser');
sprite.scale.setTo(2, 2);
sprite.inputEnabled = true;
sprite.events.onInputDown.add(function(sender, pointer){
emitter.emitX = pointer.x;
emitter.emitY = pointer.y;
emitter.emitParticle();
}, this);
http://phaser.io/sandbox/cxBVeHrx
EDIT
My actual code is based on the Phaser-ES6-Boilerplate. Even though BdRs answer solves the issue in the sandbox code, I'm not able to utilize this in my real code.
I have uploaded both the code and a running example. Hopefully someone can tell me where I have screwed things up...
Separate Phaser items don't have a z-order, instead it just depends on the order you create and add them to game. Each new sprite or emitter or group etc. will be displayed on top of all previously added items.
So, simply changing your code to something like this should work.
// first the sprite
var sprite = game.add.sprite(game.world.centerX, game.world.centerY, 'phaser');
sprite.scale.setTo(2, 2);
// then the particles in front of sprite
var emitter = game.add.emitter(game.world.centerX, game.world.centeryY);
emitter.makeParticles('phaser');
// then maybe text in front of particles and sprite
var mytest = game.add.bitmapText(10, 20, 'myfont', 'Level 1', 16);
// etc.
Btw sprites do have a .z value but that only used when it's part of a Phaser.Group, it will then be used as the display z-order but only within that group of sprites.
By default, phaser will not sort objects that get added to any group, it will just render them in the order that they get added. In your case, you can just add the emitter to the group after you add the sprite (the group in this case is the 'game' object).
Of course, having to add objects in the drawing order is not ideal, and if you need to have them sorted dynamically, not possible.
Another way is you can sort objects within a group using the 'sort' function, in which you give it the name of a parameter to sort by, and you sort whenever you need to (in some cases, in the Update callback).
Sorting every frame can be a performance hit though, especially if you have a lot of objects. Another way you could go about this is by adding groups, sorting those groups in draw order (think of them like layers), and then adding objects to those groups in any order. Any group that needs sorting within itself you can sort as well. This way, you can choose to have (for example) a background layer not needing to be sorted but everything added to that layer will be behind every other layer.
Good answers from everybody, but you are missing that every GameObject has a depth property which serves exactly the z-index purpose. This way you do not need to rely on the order of objects creation.
There is also an official
example.
Hope this helps.

UITableView too slow on main thread

I was wondering if there is any way to accelerate/speed up the UItableView/[reload data].
Even though I reload the data on the main thread using:
> [self.tableview performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:No]
I still think the results is quite slow (2 to 3 seconds to refresh each time the table is being reloaded, making a bad user experience if the user keep refreshing the table).
For a bit of background here is how it works at the moment.
1) I create my UITableView from data coming from the database on apple starting (until here no problem and I am fine if its taking few seconds).
2) I click on one of the item of the table view, which opens a new screen to modify the elements.
3) Once the elements is modified, I change the database of my table view so that the elements I changed comes at the top of my table view.
4) When I press the back button on the screen to change my elements, I implements the ViewWillAppear of my UITableView and I reload the data on the main thread.
5) What I observer is a latency of about 2/3 seconds to go from one screen to another. Removing the reload data line from my code, make the transition instantaneous. So the problem is really in the reload data.
Each row of my UITableView has the following elements inside:
A background color coming from a file (this is basically a gradient, the file is about 20Ko, it is a PNG, the file is store on the phone)
1 or 2 UIImageView (depending on the status of what I display) (about 5Ko each, very small PNG icons, the file is store on the phone)
3 UILabel
1 UIButton (inside this PNG, there is a picture, the original size of the picture may vary from 200 to 300ko, the original source is coming from internet. I use SDWebImage to load it/cache it)
I would think I may have too much information on each of my cell, as I can see a big difference in loading time when I only have one row, versus having 10rows.
Would like to have some input from the community to see if anything can be done to improve. The best would be to be able to update the UI in the background, so for example, when I am modifying my elements, I do reloaddata in the background, so that when I come back to the previous screen that shows the UITableView my view is already refreshed,
Thanks
Ok I foudn the solution on this website:
http://www.raywenderlich.com/31166/25-ios-app-performance-tips-tricks#drawing
20) Optimize Your Table Views
Table views need to scroll quickly — when they don’t, users really notice the lag.
To keep your table views scrolling smoothly, ensure that you’ve implemented all of the suggestions below:
Reuse cells by setting the correct reuseIdentifier.
Make as many views opaque as possible, including the cell itself.
Avoid gradients, image scaling, and offscreen drawing.
Cache the height of any rows if they aren’t always the same.
If the cell shows content that comes from the web, be sure to make those calls asynchronously and cache the responses. (Personnal note on this one as it was my issue: look at this other website http://keighl.com/post/sdwebimage-uitableviewcell/ that explains how to use SDWebImage in a UITableView. Very useful.
Use shadowPath to set up shadows.
Reduce the number of subviews.
Do as little work as possible in cellForRowAtIndexPath:. If you need to do some work, do it only once and cache the results.
Use the appropriate data structure to hold the information you need. Different structures have different costs for different operations.
Use rowHeight, sectionFooterHeight and sectionHeaderHeight to set constant heights instead of asking the delegate.
You say "A background color coming from a file" - this alerts me that you're doing IO work, which is generally time-consuming, I'm curious if you remmed out the loading a colour from a file if you get a significant speed increase?

UILabel not wrapping in UITableView until device rotate (iOS8)

I have a custom MvxTableViewCell that is associated with an MvxStandardTableViewSource. That Source is then applied to a UITableView. The custom table cell is defined without any Storyboard or NIB. It is laid out in code and uses AutoLayout.
this.searchResultsTable = new UITableView();
this.searchResultsTable.AccessibilityIdentifier = "SearchView_SearchResultsTable";
this.searchResultsTable.TranslatesAutoresizingMaskIntoConstraints = false;
this.searchResultsTable.RowHeight = UITableView.AutomaticDimension;
this.searchResultsTable.EstimatedRowHeight = 44.0f;
this.searchResultsTable.RegisterClassForCellReuse(typeof(CustomerItemCell), new NSString("CustomerItemCell"));
this.searchResultsTable.AllowsMultipleSelectionDuringEditing = true;
this.searchResultsTable.TableFooterView = new UIView();
this.searchResultsTableDataSource = new MvxStandardTableViewSource(this.searchResultsTable, new NSString("CustomerItemCell"));
this.searchResultsTable.Source = this.searchResultsTableDataSource;
The MVxStandardTableViewSource is databound to a ViewModel property of type List
var set = this.CreateBindingSet<SearchView, SearchViewModel>();
set.Bind(this.searchResultsTableDataSource).To(vm => vm.SearchResults);
set.Bind(this.searchBar).For(x => x.Text).To(vm => vm.CurrentSearchCriteria);
set.Apply();
This all works fine until an item in the data source causes some text wrapping in one of the UILabels and consequently a different height to the other cells.
The cell height is mostly correctly calculated but the UILabel within the
cell does not get redrawn until the device is rotated. I am using iOS AutoLayout to layout the various UIViews in the Cell.
Here are some examples of the large cell in my layout, see the
person "THISISAPATIENTWITHA-" (note this is test data not real people's data)
Initial display of cells
Same cells but device has been rotated
Still the same cells with device rotated back to original
How do I get the UILabel to redraw? We only need to support iOS8 and above.
I cannot see an event or method that gets called when the data binding has happened that would allow me to effectively tell the custom cell "You now have your subviews populated with bound data so redraw them"
The table has another issue too that is covered by this question, Implementing cell reuse for varying height cells in UITableView
Simple Repro on Github
https://github.com/munkii/TableCellResizeIssue
UPDATE:
I've forked your GitHub project and submitted a pull request. But here's my updates to your project.
https://github.com/SharpMobileCode/TableCellResizeIssue
First, you're using FluentLayout for your constraints. Nothing wrong with that actually, but that's some good info to tell others. :)
Second, in order for UITableView.AutomaticDimension to work on TableView Cells, there must be enough autolayout constraints defined in order for the cell to calculate the height of the cell. UITableView.AutomaticDimension depends on proper AutoLayout constraints.
Since you were using FluentLayout to abstract iOS AutoLayout constraints, this was not obvious as no warnings were present in the application output window. Though FluentLayout was technically correct, it however wasn't enough for UITableView.AutomaticDimension to automatically calculate each cell height.
So what I did was added a few more constraints. Look in CustomerItemCell.CreateView() in the pull request (or my github link). You can see that I added additional constraints for all the bottom labels so that they add a Bottom Constraint to the ContentView (Just like you did with this.bornLabel). This had to be applied to all the labels on the bottom of the cell. This gives AutoLayout enough information to properly calculate the cell height.
Third, This almost works, but if you rotate to Landscape, you'll notice that the long name cells will be bigger and have extra padding. To fix this, I created another class called AutoLayoutLabel that inherits from UILabel. I overrode the Bounds property so that it changes the PreferredMaxLayoutWidth to the proper width when rotated to Landscape, and back to Portrait. You then will need to use AutoLayoutLabel instead of UILabel. You'll need this for all labels that need to wrap. I'm not sure how to set PreferredMaxLayoutWidth to auto in code, but this is how to do it programmatically (which also works for iOS 7).
public class AutoLayoutLabel : UILabel
{
public override CGRect Bounds
{
get
{
return base.Bounds;
}
set
{
base.Bounds = value;
if(this.Lines == 0 && Bounds.Size.Width != PreferredMaxLayoutWidth)
{
PreferredMaxLayoutWidth = Bounds.Size.Width;
SetNeedsUpdateConstraints();
}
}
}
}
Well, that should do it!
I now have a solution to this part of my issue. Prompted by #SharpMobileCode reference to PreferredMaxLayoutWidth I decided to give that another go. Rather that setting it to Automatic (which seems impossible in code) I am setting it Explicitly, once AutoLayout has done its thing. Like this
/// <summary>
/// Lays out subviews.
/// </summary>
public override void LayoutSubviews()
{
base.LayoutSubviews();
this.nameLabel.PreferredMaxLayoutWidth = this.nameLabel.Frame.Size.Width;
}
I am no longer seeing the Labels not wrap (hurrah!) however I am seeing an issue with what looks like cell reuse. Once I scroll all of the cell off the top of the screen I can scroll it back on and it has reverted to the same height as all the other cells. I can see the label is still wrapping but the cell height is wrong.
The standard table views in MvvmCross date back to iOS4 - while the new UITableViewAutomaticDimension sizing wasn't really added until much more recently (iOS8?)
Most real apps tend to use custom cells rather than the standard ones, but if you do want to use the standard ones, then I'd guess you could try adding some code to the setters in the cell which would trigger resize recalculations - e.g. to setters in https://github.com/MvvmCross/MvvmCross/blob/3.5/Cirrious/Cirrious.MvvmCross.Binding.Touch/Views/MvxStandardTableViewCell.cs#L73
I would guess that judiciously placed calls in there to request layout recalc would cause the parent cell and table to redraw.

Resources