2
votes

I am using secondary toolbar items in my content page. I want the same menu items in iOS as in android. By default it is showing under navigation bar. How can I achieve this.

enter image description here

3
That's the design in iOS..I don't think there is a workaround unless you custom the Toobar.ColeX - MSFT

3 Answers

4
votes

I found the solution as, I created a custom PageRenderer class.

public class RightToolbarMenuCustomRenderer : PageRenderer
{

    //I used UITableView for showing the menulist of secondary toolbar items.
    List<ToolbarItem> _secondaryItems;
    UITableView table;

    protected override void OnElementChanged(VisualElementChangedEventArgs e)
    {
        //Get all secondary toolbar items and fill it to the gloabal list variable and remove from the content page.
        if (e.NewElement is ContentPage page)
        {
            _secondaryItems = page.ToolbarItems.Where(i => i.Order == ToolbarItemOrder.Secondary).ToList();
            _secondaryItems.ForEach(t => page.ToolbarItems.Remove(t));
        }
        base.OnElementChanged(e);
    }

    public override void ViewWillAppear(bool animated)
    {
        var element = (ContentPage)Element;
        //If global secondary toolbar items are not null, I created and added a primary toolbar item with image(Overflow) I         
                // want to show.
        if (_secondaryItems != null && _secondaryItems.Count > 0)
        {
            element.ToolbarItems.Add(new ToolbarItem()
            {
                Order = ToolbarItemOrder.Primary,
                Icon = "more.png",
                Priority = 1,
                Command = new Command(() =>
                {
                    ToolClicked();
                })
            });
        }
        base.ViewWillAppear(animated);
    }

    //Create a table instance and added it to the view.
    private void ToolClicked()
    {
        if (table == null)
        {
            //Set the table position to right side. and set height to the content height.
            var childRect = new RectangleF((float)View.Bounds.Width - 250, 0, 250, _secondaryItems.Count() * 56);
            table = new UITableView(childRect)
            {
                Source = new TableSource(_secondaryItems) // Created Table Source Class as Mentioned in the 
                                //Xamarin.iOS   Official site
            };
            Add(table);
            return;
        }
        foreach (var subview in View.Subviews)
        {
            if(subview == table)
            {
                table.RemoveFromSuperview();
                return;
            }
        }
        Add(table);
    }   
}

Table Source Class is Inherited UITableViewSource

public class TableSource : UITableViewSource
{

       // Global variable for the secondary toolbar items and text to display in table row
    List<ToolbarItem> _tableItems;
    string[] _tableItemTexts;
    string CellIdentifier = "TableCell";

    public TableSource(List<ToolbarItem> items)
    {
            //Set the secondary toolbar items to global variables and get all text values from the toolbar items
        _tableItems = items;
        _tableItemTexts = items.Select(a => a.Text).ToArray();
    }

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

    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; // Set default row height.
    }

    public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
    {
        //Used command to excute and deselct the row and removed the table.
        var command = _tableItems[0].Command;
        command.Execute(_tableItems[0].CommandParameter);
        tableView.DeselectRow(indexPath, true);
        tableView.RemoveFromSuperview();
    }
}

In View Model I have

public ICommand CancelCommand { get; set; }

//In constructor of ViewModel I Created Event for Command 
CancelCommand = new Command(async () => await Cancel());

private async Task Cancel()
{
    await Task.CompletedTask;
}

In Content Page Xaml I am using toolbar items as

<ContentPage.ToolbarItems>
    <ToolbarItem Text ="Cancel" Priority="1" Order="Secondary" Command="{Binding CancelCommand}"/>
</ContentPage.ToolbarItems>

and The output for the solution is same as I want.

Happy ending :). Please let me know if I can use much better approach. Ready to learn.

4
votes

Some changes to Amit Manchanda answer:

Table Source Class is Inherited UITableViewSource

public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
        {
            //Used command to excute and deselct the row and removed the table.
            var command = _tableItems[**indexPath.Row**].Command;
            command.Execute(_tableItems[**indexPath.Row**].CommandParameter);
            tableView.DeselectRow(indexPath, true);
            tableView.RemoveFromSuperview();
        }

custom PageRenderer class.

if (_secondaryItems != null && _secondaryItems.Count > 0 **&& element.ToolbarItems.FirstOrDefault(e => e.ClassId == "menu") == null**)
            {
                element.ToolbarItems.Add(new ToolbarItem()
                {
                    Order = ToolbarItemOrder.Primary,
                    Icon = "more.png",
                    Priority = 1,
                    Command = new Command(() =>
                    {
                        ToolClicked();
                    })**,
                    ClassId = "menu"**
                });
            }
0
votes

Big thanks to @Amit Manchanda and @Igor Tsapko. This worked pretty well for me.

I had the same question as @BatMaci. Here's what I did to solve it using the MessagingCenter:

In the RightToolbarMenuCustomRenderer constructor, I subscribed to a message from the MessagingCenter:

public RightToolbarMenuCustomRenderer()
{
    MessagingCenter.Subscribe<BaseViewModel>(this, AppConstants.str_iOS_TAP_OUTSIDE_CUSTOM_TOOLBAR, CloseRenderedControl);
}

public void CloseRenderedControl(object sender)
{
    if (table != null)
        ToolClicked(); // same as the ToolClicked() method Amit posted
}

Then in the markup for my page, I added a ContentView with a TapGestureRecognizer that wraps the view's content, and is bound to a Command.

<ContentView>
    <ContentView.GestureRecognizers>
        <TapGestureRecognizer Command="{Binding OnTappedCommand}" />
    </ContentView.GestureRecognizers>
    <StackLayout>
    <ListView .....

The command is an ICommand on my BaseViewModel, which is initialized in that VM's constructor, to execute my OnTappedMethod:

public BaseViewModel()
{
    OnTappedCommand = new Command(OnTappedMethod);
}

public ICommand OnTappedCommand { protected set; get; }

public void OnTappedMethod()
{
    if(Device.RuntimePlatform == Device.iOS)
        MessagingCenter.Send<BaseViewModel>(this, AppConstants.str_iOS_TAP_OUTSIDE_CUSTOM_TOOLBAR);
}

This pretty much just simulates clicking the custom control closed again. I wanted to post this to save someone some time. I should point out I'm new to Xamarin, and have only tested on one iOS device (iPhone 7 running 12.4.1).