Skip to content

Instantly share code, notes, and snippets.

@marcinkuptel
Last active September 3, 2017 19:42
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save marcinkuptel/c0e5fa78971d2fad9ba8 to your computer and use it in GitHub Desktop.
Save marcinkuptel/c0e5fa78971d2fad9ba8 to your computer and use it in GitHub Desktop.
SlideMenuController - Advanced UI with Xamarin.iOS
using System;
using Foundation;
using UIKit;
using System.Collections.Generic;
using System.Diagnostics;
using CoreGraphics;
namespace iOS
{
/// <summary>
/// This controller represents the menu component used, for example, on the events
/// overview page. It can display multiple tabs, each with a separate list of items.
/// </summary>
public partial class SlideMenuController : UIViewController, IMenuNavigationViewDelegate
{
public enum TouchHandler {
None = 0,
MenuNavigationView,
TableView
}
public List<UITableView> TableViews { get; private set; }
UITableView CurrentTableView;
int currentPageIndex;
bool MenuNavigationViewTransparent { get; set; }
MenuNavigationViewStyle MenuStyle { get; set; }
bool scrollingHorizontally { get; set; }
public nfloat ContentOffset {
set {
foreach(UITableView tv in TableViews)
{
tv.ContentOffset = new CGPoint (0, value);
}
}
}
public nfloat HorizontalOffset {
set {
contentScrollView.ContentOffset = new CGPoint(value, contentScrollView.ContentOffset.Y);
}
}
public UITableView this[int index] {
get {
return TableViews[index];
}
}
WeakReference<ISlideMenuControllerDataSource> _dataSource = new WeakReference<ISlideMenuControllerDataSource>(null);
public ISlideMenuControllerDataSource DataSource {
get {
ISlideMenuControllerDataSource source;
_dataSource.TryGetTarget(out source);
return source;
}
private set {
_dataSource = new WeakReference<ISlideMenuControllerDataSource>(value);
}
}
public nfloat MenuViewHeight
{
get
{
return 40f;
}
}
public SlideMenuController (ISlideMenuControllerDataSource source, bool menuTransparent, MenuNavigationViewStyle menuStyle)
: base ("SlideMenuController", NSBundle.MainBundle)
{
DataSource = source;
TableViews = new List<UITableView>();
MenuNavigationViewTransparent = menuTransparent;
MenuStyle = menuStyle;
}
public override void DidReceiveMemoryWarning ()
{
// Releases the view if it doesn't have a superview.
base.DidReceiveMemoryWarning ();
// Release any cached data, images, etc that aren't in use.
}
public override void ViewDidLoad ()
{
base.ViewDidLoad();
View.BackgroundColor = UIColor.Clear;
contentScrollView.PagingEnabled = true;
contentScrollView.ShowsHorizontalScrollIndicator = false;
contentScrollView.Scrolled += HandleContentScrollViewScrolled;
currentPageIndex = DataSource.InitialTabIndex();
AddContentViewConstraints();
AddTableViews();
ConfigureMenuNavigationView();
}
public override void ViewDidLayoutSubviews()
{
base.ViewDidLayoutSubviews();
SetCurrentPage(currentPageIndex);
}
void HandleContentScrollViewScrolled(object o, EventArgs e)
{
currentPageIndex = (int)((contentScrollView.ContentOffset.X + contentScrollView.Frame.Size.Width / 2)
/ contentScrollView.Frame.Size.Width);
CurrentTableView = TableViews[currentPageIndex];
menuNavigationView.SelectMenuItemAtIndex(currentPageIndex);
}
void SetCurrentPage(int index)
{
CurrentTableView = TableViews[index];
var xOffset = HorizontalOffsetForSection(index);
var offset = new CGPoint (xOffset, 0);
contentScrollView.SetContentOffset(offset, false);
}
#region - Content View Constraints
void AddContentViewConstraints()
{
contentView.TranslatesAutoresizingMaskIntoConstraints = false;
View.AddConstraint (
NSLayoutConstraint.Create(contentView, NSLayoutAttribute.Bottom, NSLayoutRelation.Equal, View, NSLayoutAttribute.Bottom, 1, 0)
);
}
#endregion
#region - Table views
/// <summary>
/// This method adds a certain number of table views to the scroll view (depending on the data source).
/// Table views are placed next to each other from left to right. They all have the same size.
/// </summary>
void AddTableViews()
{
int numberOfSections = DataSource.NumberOfTabs();
for(int i = 0; i < numberOfSections; i++)
{
var tableView = new UITableView (new CGRect ());
tableView.TranslatesAutoresizingMaskIntoConstraints = false;
SlideMenuTableViewSource source = DataSource.TableViewSourceForTab(i);
tableView.Source = source;
tableView.SeparatorStyle = UITableViewCellSeparatorStyle.None;
contentView.AddSubview (tableView);
NSLayoutConstraint[] vertical = TableViewVerticalConstraints (tableView);
contentView.AddConstraints (vertical);
var width = NSLayoutConstraint.Create (tableView, NSLayoutAttribute.Width, NSLayoutRelation.Equal, View, NSLayoutAttribute.Width, 1, 0);
View.AddConstraint (width);
TableViews.Add (tableView);
}
NSLayoutConstraint[] horizontal = TableViewHorizontalConstraints (TableViews);
contentView.AddConstraints (horizontal);
}
NSLayoutConstraint[] TableViewVerticalConstraints(UITableView tableView)
{
var dict = new NSDictionary("tableView", tableView);
NSLayoutConstraint[] constraints = NSLayoutConstraint.FromVisualFormat("V:|[tableView]|", 0, null, dict);
return constraints;
}
static NSLayoutConstraint[] TableViewHorizontalConstraints(IList<UITableView> tableViews)
{
Debug.Assert (tableViews.Count > 0);
var constraints = new List<NSLayoutConstraint> ();
for(int i = 0; i < tableViews.Count - 1; i++)
{
UITableView left = tableViews [i];
UITableView right = tableViews [i + 1];
var sideBySide = NSLayoutConstraint.FromVisualFormat ("H:[left][right]", 0, null, new NSDictionary ("left", left, "right", right));
var constraint = NSLayoutConstraint.Create (left, NSLayoutAttribute.Width, NSLayoutRelation.Equal, right, NSLayoutAttribute.Width, 1, 0);
constraints.Add (constraint);
constraints.AddRange (new List<NSLayoutConstraint>(sideBySide));
}
UITableView first = tableViews [0];
NSLayoutConstraint[] firstConstraints = NSLayoutConstraint.FromVisualFormat ("H:|[first]", 0, null, new NSDictionary ("first", first));
constraints.AddRange (new List<NSLayoutConstraint>(firstConstraints));
UITableView last = tableViews [tableViews.Count - 1];
NSLayoutConstraint[] lastConstraints = NSLayoutConstraint.FromVisualFormat ("H:[last]|", 0, null, new NSDictionary ("last", last));
constraints.AddRange (new List<NSLayoutConstraint>(lastConstraints));
return constraints.ToArray ();
}
public void ReloadTableViews()
{
int numberOfSections = DataSource.NumberOfTabs();
for(int i = 0; i < numberOfSections; i++)
{
var tableView = TableViews[i];
SlideMenuTableViewSource source = DataSource.TableViewSourceForTab(i);
tableView.Source = source;
tableView.ReloadData();
}
}
#endregion
#region - Menu Navigation View
void ConfigureMenuNavigationView()
{
var sectionTitles = DataSource.TabNames();
menuNavigationView.SectionTitles = sectionTitles;
menuNavigationView.menuDelegate = this;
menuNavigationView.Style = MenuStyle;
menuNavigationView.SelectMenuItemAtIndex(DataSource.InitialTabIndex(), false);
if(MenuNavigationViewTransparent)
{
menuNavigationView.BackgroundColor = UIColor.FromPatternImage(UIImage.FromBundle("FadedBlue"));
}
}
#endregion
#region - IMenuNavigationViewDelegate
nfloat HorizontalOffsetForSection(int sectionIndex)
{
return sectionIndex * View.Frame.Size.Width;
}
public bool MenuNavigationViewButtonTapped(int buttonIndex, string buttonTitle)
{
if(scrollingHorizontally)
return false;
CurrentTableView = TableViews[buttonIndex];
currentPageIndex = buttonIndex;
var xOffset = HorizontalOffsetForSection (buttonIndex);
var offset = new CGPoint (xOffset, 0);
if(contentScrollView.ContentOffset.X != xOffset)
{
contentScrollView.SetContentOffset (offset, true);
contentScrollView.Scrolled -= HandleContentScrollViewScrolled;
scrollingHorizontally = true;
contentScrollView.ScrollAnimationEnded += ScrollAnimationEnded;
return true;
}
else
{
return false;
}
}
void ScrollAnimationEnded(object o, EventArgs e)
{
contentScrollView.Scrolled += HandleContentScrollViewScrolled;
contentScrollView.ScrollAnimationEnded -= ScrollAnimationEnded;
scrollingHorizontally = false;
}
#endregion
public void ReloadRowInSection(int sectionIndex, int rowIndex)
{
var tableView = TableViews[sectionIndex];
tableView.ReloadRows(new []{ NSIndexPath.FromRowSection((nint)rowIndex, 0) }, UITableViewRowAnimation.None);
}
public Tuple<TouchHandler, int> HandleTouchAtLocation(CGPoint location)
{
if(menuNavigationView.Frame.Contains(location))
{
var converted = View.ConvertPointToView(location, menuNavigationView);
int buttonIndex = menuNavigationView.HandleTouchAtLocation(converted);
if(buttonIndex != int.MaxValue) {
return new Tuple<TouchHandler, int>(TouchHandler.MenuNavigationView, buttonIndex);
} else {
return new Tuple<TouchHandler, int>(TouchHandler.None, 0);
}
} else {
try {
var converted = View.ConvertPointToView(location, CurrentTableView);
var indexPath = CurrentTableView.IndexPathForRowAtPoint(converted);
if(indexPath != null) {
var cell = CurrentTableView.CellAt(indexPath);
cell.PerformTapAnimation(CurrentTableView.ConvertPointToView(converted, cell));
CurrentTableView.Source.RowSelected(CurrentTableView, indexPath);
}
}
catch(Exception e)
{
Console.WriteLine(e);
return new Tuple<TouchHandler, int>(TouchHandler.None, 0);
}
return new Tuple<TouchHandler, int>(TouchHandler.TableView, 0);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment