1

I have a ProductList of type ObservableCollection<Product> where Product is a class given below:

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Path { get; set; }
}

In my Xamarin Forms iOS Application, I have a ListView whose ItemSource is ProductList. When number of Products in the ProductList is so that the height of ListView is not enough to show all of them, additional items can be reached by scrolling the ListView. However, I want the ScrollBar that is displayed only when scrolling the ListView to be displayed always. Is it possible or should I try another UI besides ListViev to be able to always show ScrollBar.

There are some solutions for Xamarin.Android but I couldn't find any effective solution for the iOS App.

Thank you very much...

Abdullah
  • 535
  • 1
  • 5
  • 10
  • https://stackoverflow.com/questions/1888647/uiscrollview-showing-the-scroll-bar maybe help. – ColeX Aug 23 '17 at 10:56

1 Answers1

1

As @Cole commented, the flashScrollIndicators can only show the indicator a short time.

So, you have to customize a scroll indicator.

Create a custom renderer of the ListView and add your custom scroll indicator, like this:

    public class MyListViewRenderer : ListViewRenderer
    {
        public UIView bar;
        protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
        {
            base.OnElementChanged(e);

            //Hide the default Scroll Indicator.
            Control.ShowsVerticalScrollIndicator = false;


            //Set Delegate
            CustomScrollDelegate customScrollDelegate = new CustomScrollDelegate();
            Control.Delegate = customScrollDelegate;

            //Create the background view of custom indicator.
            double frameHeight = Control.Frame.Size.Height;
            double frameWidth = Control.Frame.Size.Width;

            double barBackgroundWidth = 6;
            double statusBarHeight = 20;

            UIView barBackgroundView = new UIView();
            CGRect barBVRect = new CGRect(frameWidth - barBackgroundWidth, statusBarHeight, barBackgroundWidth, frameHeight);
            barBackgroundView.Frame = barBVRect;
            barBackgroundView.BackgroundColor = UIColor.Gray;
            barBackgroundView.Layer.CornerRadius = 2;
            barBackgroundView.Layer.MasksToBounds = true;


            //Create the bar of the custom indicator.
            bar = new UIView();
            CGRect barRect = new CGRect(1, 0, 4, 0);
            bar.Frame = barRect;
            bar.BackgroundColor = UIColor.Black;
            bar.Layer.CornerRadius = 2;
            bar.Layer.MasksToBounds = true;

            //Add the views to the superview of the tableview.
            barBackgroundView.AddSubview(bar);
            Control.Superview.AddSubview(barBackgroundView);

            //Transfer the bar view to delegate.
            customScrollDelegate.bar = bar;

        }

        public override void LayoutSubviews()
        {
            base.LayoutSubviews();
            Console.WriteLine("End of loading!!!");
            double contentHeight = Control.ContentSize.Height;
            double frameHeight = Control.Frame.Size.Height;
            double barHeight = frameHeight * frameHeight / contentHeight;


            //Reset the bar height when the table view finishes loading.
            CGRect barRect = new CGRect(bar.Frame.X, bar.Frame.Y, bar.Frame.Width, barHeight);
            bar.Frame = barRect;
        }

    }

Implement the Scrolled delegate which tracks the scrolling action of the scrollView. You can update the position of the indicator in the delegate.

    public class CustomScrollDelegate : UIKit.UITableViewDelegate
    {
        public UIView bar;
        double barY;

        public override void Scrolled(UIScrollView scrollView)
        {
            double y = scrollView.ContentOffset.Y;
            double contentHeight = scrollView.ContentSize.Height;
            double frameHeight = scrollView.Frame.Size.Height;

            double barHeight = frameHeight * frameHeight / contentHeight;
            barY = y / (contentHeight - frameHeight) * (frameHeight - barHeight);

            //Cut the bar Height when it over the top.
            if (barY < 0)
            {
                barHeight = barHeight + barY;
                barY = 0;
            }

            //Cut the bar height when it over the bottom.
            if (barY > (frameHeight - barHeight))
            {
               barHeight = barHeight - (barY - (frameHeight - barHeight));
            }

            //Reset the barView rect. Let's move!!!
            CGRect barRect = new CGRect(bar.Frame.X, barY, bar.Frame.Width, barHeight);
            bar.Frame = barRect;

        }

    }

It works like this:

enter image description here

Kevin Li
  • 2,258
  • 1
  • 9
  • 18
  • This is exactly what I asked for. However there are some points that I couldn't understand. – Abdullah Aug 25 '17 at 07:42
  • 1 ) Isn't the `barY` top point of the scrollbar? This is because when scrollbar is at the top, value of `y` is `(contentHeight - frameHeight)/2`, so value of `barY` is `(frameHeight - barHeight)/2`. Shouldn't it be 0 when scrollbar is at the top? – Abdullah Aug 25 '17 at 07:50
  • 2) //Cut the bar height when it over the bottom. Why did you choose `(barY > (contentHeight - frameHeight))`? I think when it is over the bottom `barY > frameHeight - barHeight` – Abdullah Aug 25 '17 at 07:52
  • @Jose, 1). Yes, `barY` is the top of the scrollbar. It should be 0 at the top. How do you get the `y` is `(contentHeight - frameHeight)/2` ? `y` should be 0 and `barY` is 0 too. – Kevin Li Aug 25 '17 at 08:20
  • @Jose, 2). Sorry, it is my mistake. I have updated my answer. – Kevin Li Aug 25 '17 at 08:23
  • Another thing: `barHeight` decreases when it hits the top and bottom even if I comment out the two conditions about the `barY`. Do I misinterpret the function of those two `if`s – Abdullah Aug 25 '17 at 08:24
  • @Jose, No, you got the point. The `if`s are used to decrease the `barHeight` when it hit the top and bottom as the native indicator action. – Kevin Li Aug 25 '17 at 08:30
  • 1) `y` is defined as the offset for the origin of the content inside of a scrollview, relative to the origin of the scrollview itself. When scrollbar is at the top; origin of the scrollview is at `frameHeight/2` and origin of the content is at `contentHeight/2`, both referenced to the top of scrollview. Therefore, distance between them becomes `(contentHeight - frameHeight)/2` – Abdullah Aug 25 '17 at 08:32
  • Then why `barHeight` decreases when I delete those two conditions? – Abdullah Aug 25 '17 at 08:34
  • No, they didn't decrease. They just move out of the screen. As my gif, if I remove the `if` for top, it will move onto the status bar. – Kevin Li Aug 25 '17 at 08:39
  • Thank you very much for your interest @Kevin. This helped me a lot to improve my understanding of custom renderers and UIViews – Abdullah Aug 25 '17 at 08:48
  • This solution doesn't work for me, I'm using UIKit.UIScrollViewDelegate – Aminur Rashid Jan 30 '18 at 10:44