locked
ListView, iOS, HasUnevenRows = true and ObservableCollection RRS feed

  • Question

  • User48227 posted

    My use case is that I'm attempting to implement an infinitely scrollable listview which has uneven rows.

    I've already logged a bug regarding the behaviour of the ItemAppearing event when using hasunevenrows = true (i.e. it fires for all viewcells at load time, not when they actually show) however having worked around this issue, I have discovered another issue.

    I am binding the ListView to an ObservableCollection so that I can add new items to the ListView when it scrolled within some distance of the bottom.

    The problem is that when adding items to the bound ObservableCollection it breaks the ability to scroll upwards. (Items jump around and appear to have lost their calculated size).

    Please note, the items do not need to be added as a result of scrolling to the end of the list. To reproduce this issue a button or any other trigger will suffice.

    I think that this scenario may be a fairly common one that people will try to implement (though I may be wrong) so it might be worth a sample in the xamarin forms samples github project.. I can see 3 outcomes.

    1) you may find that this is not a scenario that xamarin forms can currently support due to this issue + the itemappearing bug I have already reported, but you think it is common enough to fix

    2) I have gone about this the wrong way and it works perfectly if you do it correctly (I'd welcome this one too!) or

    3) its not supported and can't easily be supported the way things are

    Wednesday, March 11, 2015 12:55 PM

All replies

  • User49034 posted

    Hi Martin,

    I implemented something similar and it works fine.

    ` listView.ItemAppearing += (sender, e) => { if(IsBusy || contents.Count == 0) return;

                //hit bottom! 
                if(((Message) e.Item).id == contents[contents.Count - 1].id && CurrentPage < TotalPages) { 
                    this.IsBusy = true;
                    int NextPage = CurrentPage + 1;
                    Debug.WriteLine("Loading more ... Page: " + NextPage);
                    this.LoadContent (NextPage);
                    this.IsBusy = false;
                } 
            }; 
    

    ViewCell protected override void OnBindingContextChanged () { base.OnBindingContextChanged (); var message = (Message)BindingContext;

            // rough translation of character-count to cell height
            // doesn't always work, but close enough for now
            if (message.topic.Length > 75)
                this.Height = 110;
            else if (message.topic.Length > 60)
                this.Height = 80; 
            else if (message.topic.Length > 30)
                this.Height = 70;
            else
                this.Height = 60;
        }`
    
    Wednesday, March 11, 2015 1:24 PM
  • User48227 posted

    After a little more research it seems the issue is more or less as described in this stackoverflow post:

    http://stackoverflow.com/questions/27996438/jerky-scrolling-after-updating-uitableviewcell-in-place-with-uitableviewautomati

    The following comment seems to apply: "The automatic row heights just aren't usable for tables with a lot of rows with much variance of height. For reliable smooth scrolling you must give correct results in the heightForRowAtIndexPath method, preferably with cached row heights. Otherwise you will get jerkiness when the tableView needs to update it's contentSize, especially in cases like when you push or show another view controller and come back. Unfortunately automatic row heights are only usable for simple tableViews with a few rows"

    I can confirm this problem exists not just when I add new items to my ObservableList but also when I push another view and come back (as per the comment)

    It seems this is not so much a the fault of xamarin forms, but more of iOS. I'm astonished that it is so difficult on this platform to achieve nothing more than present a list of variable height items; I've been attempting (more or less) since xamarin forms was first released to create an app which just allows a user to read a feed of articles but seems as if this is one of the most difficult things to do on iOS (and as a result xamarin forms). Hopefully one day this will be an easy thing to do!

    Wednesday, March 11, 2015 1:25 PM
  • User48227 posted

    Thanks @SolaAjayi

    This is probably a reasonable option if you want to approximate the height of your rows (I have seen something similar proposed before on these forums) however I need something more accurate given how many lines of text I am aiming to display in this viewcell.

    I am very pleased to see the recent updates to xamarin forms which enable viewcells to size themselves according to their content. You don't even need to set the height yourself (as per your code example). Unfortunately it is the fact that this is quite brittle at the moment (as per my comments above) that is my current issue.

    I appreciate your assistance though!

    Wednesday, March 11, 2015 1:45 PM
  • User48227 posted

    Here is a screencast of this issue.. Please note scrolling upwards. Also note that I don't actually need to append items to the list to demonstrate this issue, I could have navigated to another page and returned again. The issue would also have presented

    Wednesday, March 11, 2015 2:04 PM
  • User62217 posted

    @MartinBooth i had similar problem but it worked with below settings

    http://forums.xamarin.com/discussion/comment/111229/#Comment_111229

    Saturday, March 21, 2015 7:40 AM
  • User20002 posted

    Thanks @MartinBooth for reporting this issue. We had the same exact problem. We worked it around by setting HasUnevenRow to false (only on iOS) and setting ListView.RowHeight (ViewCell.Height doesn't work).

    This is I think same as @Parth2651's solution above.

    Monday, April 6, 2015 1:15 PM
  • User48227 posted

    Yep I guess Parth's solution may help some people, but correct me if I'm wrong, its just the same as the hack posted here http://forums.xamarin.com/discussion/comment/64923/#Comment_64923 isn't it?

    Unfortunately, trying to guess the height of the listview cell based on the number of characters in a string is never going to be accurate enough when there is more than a few lines of content in the listview cell.

    A couple of issues were mentioned by CraigDunn in his post but (in addition) even just a line break will affect this.

    Anyway, hopefully someone from xamarin picks up this issue before too long so that the hasunevenrows property can become usable

    Tuesday, April 7, 2015 12:24 AM
  • User342362 posted

    I think this is still an active problem, I found a workaround with a ListView custom renderer. Using HasUnevenRows with UITableView.AutomaticDimension, you have to calculate the height of each cell on WillDisplay event and save the given height in a Dictionary, then use the stored value on EstimatedHeight.

    public class CustomListViewSource : UITableViewSource
        {     
            private readonly Dictionary<int, double> cachedHeights = new Dictionary<int, double>();
        }
    ...
    public override void WillDisplay(UITableView tableView, UITableViewCell cell, NSIndexPath indexPath)
            {                        
                cachedHeights[indexPath.Row] = cell.Frame.Size.Height;
            }
    ...
    public override nfloat GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
            {
                var cellForPath = this.GetCellForPath(indexPath);
        ...          
                return UITableView.AutomaticDimension;
        }
    
    public override nfloat EstimatedHeight(UITableView tableView, NSIndexPath indexPath)
            {            
                return this.cachedHeights.ContainsKey(indexPath.Row) ? (nfloat)this.cachedHeights[indexPath.Row] : UITableView.AutomaticDimension;
            }
    
    Sunday, September 3, 2017 6:15 PM
  • User262996 posted

    hey @darkeagle I am facing a similar problem but wasn't able to get what you suggested to work, would you be able to provide more code for how you got it working on your end?

    Tuesday, December 5, 2017 8:22 AM
  • User342362 posted

    hi @yawno Here the code you need:

    public class CustomListViewSource : UITableViewSource
        {
            //vars
            public IList<Item> tableItems;
            public CustomListView list;
            readonly NSString cellIdentifier = new NSString("TableCell");
            private readonly UITableViewSource source;
            private readonly Dictionary<int, double> cachedHeights = new Dictionary<int, double>();
            PropertyInfo specialProperty;
            AppDelegate ad;
    
            public CustomListViewSource(CustomListView view, UITableViewSource dataSource, PropertyInfo prop)
            {           
                tableItems = view.ItemsSource as IList<Item>;
                list = view;
                source = dataSource;
                specialProperty = prop;
                ad = (AppDelegate)UIApplication.SharedApplication.Delegate;
            }
    
            public void ClearRowHeightCache()
            {
                this.cachedHeights.Clear();
            }
    
    
            public override nint RowsInSection(UITableView tableview, nint section)
            {
                return source.RowsInSection(tableview, section);
            }
    
            public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
            {
                return source.GetCell(tableView, indexPath);
            }
    
            public override void WillDisplay(UITableView tableView, UITableViewCell cell, NSIndexPath indexPath)
            {
                cachedHeights[indexPath.Row] = cell.Frame.Size.Height;
                cell.SelectionStyle = UITableViewCellSelectionStyle.None;
            }
    
            public override nfloat GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
            {
                return UITableView.AutomaticDimension;
            }
    
            public override nfloat EstimatedHeight(UITableView tableView, NSIndexPath indexPath)
            {          
                return this.cachedHeights.ContainsKey(indexPath.Row) ? (nfloat)this.cachedHeights[indexPath.Row] : UITableView.AutomaticDimension;
            }
    
            private Cell GetCellForPath(NSIndexPath indexPath)
            {
                var templatedItemsList = (IReadOnlyList<Cell>)specialProperty.GetValue(this.list);
                return templatedItemsList[indexPath.Row];
            }
    
            public override void Scrolled(UIScrollView scrollView)
            {
                if (ad.KeyOn)
                {
                    UIApplication.SharedApplication.KeyWindow.EndEditing(true);
                }
            }
    
        }
    

    Let me know if you need more help.

    Tuesday, December 5, 2017 9:10 AM
  • User262996 posted

    thanks a lot @darkeagle what I am still no sure about is how do you connect this to your customrenderer for the listview?

    Tuesday, December 5, 2017 9:53 AM
  • User342362 posted

    @yawno you can follow the official guidelines on xamarin forms guides and use my code in the table view source.

    developer.xamarin.com/guides/xamarin-forms/application-fundamentals/custom-renderer/listview/

    Tuesday, December 5, 2017 1:44 PM