David Shochet
David Shochet

Reputation: 5395

Secondary Toolbar items not fitting the screen

I have a Xamarin.Forms app with FreshMvvm, and I use secondary ToolbarItems. To do it in iOS, I had to make a custom renderer (unlike in Android). I was given a solution on how to implement it here: ToolbarItems do not look right in iOS

This solution works perfectly for me. But by now, the toolbar menu grew longer, and some of its elements do not fit the iPhone's screen. I can slide the menu and see all the elements, but as soon as I release the screen, the view jumps back up, and while it is held by a finger, the elements are not clickable. How can this be solved? Can the menu made to wrap, or something else? On Android, the menu stays where I scroll and I can click every item. Can it made stay where scrolled on iOS, too?

Here is my renderer's code:

using CoreGraphics;
using MobileApp.iOS.Renderers;
using MobileApp.iOS.Services;
using MobileApp.Pages;
using MobileApp.Services;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using UIKit;
using Xamarin.Forms;
using Xamarin.Forms.Platform.iOS;

[assembly: ExportRenderer(typeof(CustomToolbarContentPage), 
typeof(RightToolbarMenuCustomRenderer))]
namespace MobileApp.iOS.Renderers
{
class RightToolbarMenuCustomRenderer : PageRenderer
{
    private List<ToolbarItem> _primaryItems;
    private List<ToolbarItem> _secondaryItems;
    private UITableView _table;
    private UITapGestureRecognizer _tapGestureRecognizer;
    private UIView _transparentView;

    protected override void OnElementChanged(VisualElementChangedEventArgs e)
    {
        if (e.NewElement is IAddToolbarItem item)
        {
            item.ToolbarItemAdded += Item_ToolbarItemAdded;
        }
        base.OnElementChanged(e);
    }

    private void Item_ToolbarItemAdded(object sender, System.EventArgs e)
    {
        if (Element is ContentPage page)
        {
            _primaryItems = page.ToolbarItems.Where(i => i.Order == ToolbarItemOrder.Primary).ToList();
            _secondaryItems = page.ToolbarItems.Where(i => i.Order == ToolbarItemOrder.Secondary).ToList();
            _secondaryItems.ForEach(t => page.ToolbarItems.Remove(t));
        }

        var element = (ContentPage)Element;

        if (_secondaryItems?.Count == 0 && element.ToolbarItems.Any(a => (a.IconImageSource as FileImageSource)?.File == "more.png"))
        {
            element.ToolbarItems.Clear();
        }
        else if (_secondaryItems?.Count >= 1 && !element.ToolbarItems.Any(a => (a.IconImageSource as FileImageSource)?.File == "more.png"))
        {
            element.ToolbarItems.Add(new ToolbarItem()
            {
                Order = ToolbarItemOrder.Primary,
                IconImageSource = "more.png",
                Priority = 1,
                Command = new Command(ToggleDropDownMenuVisibility)
            });
        }
    }

    private void ToggleDropDownMenuVisibility()
    {
        if (!DoesTableExist())
        {
            if ((View?.Subviews != null)
                && (View.Subviews.Length > 0)
                && (View.Bounds != null)
                && (_secondaryItems != null)
                && (_secondaryItems.Count > 0))
            {
                _table = OpenDropDownMenu(Element as IAddToolbarItem);
                Add(_table);
            }
        }
        else
            CloseDropDownMenu();
    }

    private bool DoesTableExist()
    {
        if (View?.Subviews != null)
        {
            foreach (var subview in View.Subviews)
            {
                if (_table != null && subview == _table)
                {
                    return true;
                }
            }
        }
        if (_tapGestureRecognizer != null)
        {
            _transparentView?.RemoveGestureRecognizer(_tapGestureRecognizer);
            _tapGestureRecognizer = null;
        }
        _table = null;
        _tapGestureRecognizer = null;
        return false;
    }

    private UITableView OpenDropDownMenu(IAddToolbarItem secondaryMenuSupport)
    {
        _transparentView = _transparentView = new UIView(new CGRect(0, 0, View.Bounds.Width, View.Bounds.Height))
        {
            BackgroundColor = UIColor.FromRGBA(0, 0, 0, 0)
        };
        _tapGestureRecognizer = new UITapGestureRecognizer(CloseDropDownMenu);
        _transparentView.AddGestureRecognizer(_tapGestureRecognizer);
        Add(_transparentView);

        UITableView table = null;
        if (_secondaryItems != null && _secondaryItems.Count > 0)
        {
            table = new UITableView(GetPositionForDropDownMenu(secondaryMenuSupport.RowHeight, secondaryMenuSupport.TableWidth))
            {
                Source = new TableSource(_secondaryItems, _transparentView),
                ClipsToBounds = false
            };

            table.ScrollEnabled = true;
            table.Layer.ShadowColor = secondaryMenuSupport.ShadowColor.ToCGColor();
            table.Layer.ShadowOpacity = secondaryMenuSupport.ShadowOpacity;
            table.Layer.ShadowRadius = secondaryMenuSupport.ShadowRadius;
            table.Layer.ShadowOffset = new SizeF(secondaryMenuSupport.ShadowOffsetDimension, secondaryMenuSupport.ShadowOffsetDimension);
            table.BackgroundColor = secondaryMenuSupport.MenuBackgroundColor.ToUIColor();
        }
        return table;
    }

    public override void ViewWillDisappear(bool animated)
    {
        CloseDropDownMenu();
        base.ViewWillDisappear(animated);
    }

    private RectangleF GetPositionForDropDownMenu(float rowHeight, float tableWidth)
    {
        if ((View?.Bounds != null)
            && (_secondaryItems != null)
            && (_secondaryItems.Count > 0))
        {
            return new RectangleF(
                (float)View.Bounds.Width - tableWidth,
                0,
                tableWidth,
                _secondaryItems.Count() * rowHeight);
        }
        else
        {
            return new RectangleF(0.0f, 0.0f, 0.0f, 0.0f);
        }
    }

    private void CloseDropDownMenu()
    {
        if (_table != null)
        {
            if (_tapGestureRecognizer != null)
            {
                _transparentView?.RemoveGestureRecognizer(_tapGestureRecognizer);
                _tapGestureRecognizer = null;
            }

            if (View?.Subviews != null)
            {
                foreach (var subview in View.Subviews)
                {
                    if (subview == _table)
                    {
                        _table.RemoveFromSuperview();
                        break;
                    }
                }

                if (_transparentView != null)
                {
                    foreach (var subview in View.Subviews)
                    {
                        if (subview == _transparentView)
                        {
                            _transparentView.RemoveFromSuperview();
                            break;
                        }
                    }
                }
            }
            _table = null;
            _transparentView = null;
        }
    }

    public override void ViewDidLayoutSubviews()
    {
        base.ViewDidLayoutSubviews();

        if (_table != null)
        {
            if (Element is IAddToolbarItem secondaryMenuSupport)
                PositionExistingDropDownMenu(secondaryMenuSupport.RowHeight, secondaryMenuSupport.TableWidth);
        }
    }

    private void PositionExistingDropDownMenu(float rowHeight, float tableWidth)
    {
        if ((View?.Bounds != null)
            && (_secondaryItems != null)
            && (_secondaryItems.Count > 0)
            && (_table != null))
        {
            _table.Frame = GetPositionForDropDownMenu(rowHeight, tableWidth);
        }
    }
}
}

ADDITION:

public class TableSource : UITableViewSource
{
    List<ToolbarItem> _tableItems;
    string[] _tableItemTexts;
    string CellIdentifier = "TableCell";
    UIView _tableSuperView = null;

    public TableSource(List<ToolbarItem> items, UIView tableSuperView)
    {
        _tableItems = items;
        _tableSuperView = tableSuperView;
        _tableItemTexts = items.Select(a => a.Text).ToArray();
    }

    public override nint RowsInSection(UITableView tableview, nint section)
    {
        return _tableItemTexts?.Length ?? 0;
    }

    public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
    {
        UITableViewCell cell = tableView.DequeueReusableCell(CellIdentifier);
        string item = _tableItemTexts[indexPath.Row];
        if (cell == null)
        { cell = new UITableViewCell(UITableViewCellStyle.Default, CellIdentifier); }
        cell.TextLabel.Text = item;
        return cell;
    }

    public override nfloat GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
    {
        return 56;
    }

    public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
    {
        var command = _tableItems[indexPath.Row].Command;
        command.Execute(_tableItems[indexPath.Row].CommandParameter);
        tableView.DeselectRow(indexPath, true);
        tableView.RemoveFromSuperview();
        if (_tableSuperView != null)
        {
            _tableSuperView.RemoveFromSuperview();
        }
    }
}

public interface IAddToolbarItem
{
    event EventHandler ToolbarItemAdded;
    Color CellBackgroundColor { get; }
    Color CellTextColor { get; }
    Color MenuBackgroundColor { get; }
    float RowHeight { get; }
    Color ShadowColor { get; }
    float ShadowOpacity { get; }
    float ShadowRadius { get; }
    float ShadowOffsetDimension { get; }
    float TableWidth { get; }
}

Here you can download the project that reproduces the issue: https://github.com/DavidShochet/Public

Upvotes: 0

Views: 293

Answers (1)

nevermore
nevermore

Reputation: 15816

Well, I still can't reproduce the problem with your updated code. I don't if it is a solution and I just want to clarify my comment here.

In your code, you add the _table to the View:

 _table = OpenDropDownMenu(Element as IAddToolbarItem);
 Add(_table);

What I want you to have a try is add the _table to _transparentView :

  _table = OpenDropDownMenu(Element as IAddToolbarItem);
  //Add(_table);
  _transparentView.Add(_table);

It would be better if you can provide us a Minimal, Reproducible Example so that I can debug it on my side.

Update:

I found the problem is here, you set the height of table _secondaryItems.Count() * rowHeight) which is longer then the view when the toolbar menu grew longer:

private RectangleF GetPositionForDropDownMenu(float rowHeight, float tableWidth)
{
    if ((View?.Bounds != null)
        && (_secondaryItems != null)
        && (_secondaryItems.Count > 0))
    {
        return new RectangleF(
            (float)View.Bounds.Width - tableWidth,
            0,
            tableWidth,

            //here is the cause
            _secondaryItems.Count() * rowHeight);
    }
    else
    {
        return new RectangleF(0.0f, 0.0f, 0.0f, 0.0f);
    }
}

Solution: change the height of tablview to (float)View.Bounds.Height:

private RectangleF GetPositionForDropDownMenu(float rowHeight, float tableWidth)
{
    if ((View?.Bounds != null)
        && (_secondaryItems != null)
        && (_secondaryItems.Count > 0))
    {
        return new RectangleF(
            (float)View.Bounds.Width - tableWidth,
            0,
            tableWidth,
            (float)View.Bounds.Height);
    }
    else
    {
        return new RectangleF(0.0f, 0.0f, 0.0f, 0.0f);
    }
}

Upvotes: 1

Related Questions