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.
3 Answers
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.
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"**
});
}
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).