Instantly share code, notes, and snippets.
Last active
September 3, 2017 19:42
-
Save marcinkuptel/c0e5fa78971d2fad9ba8 to your computer and use it in GitHub Desktop.
SlideMenuController - Advanced UI with Xamarin.iOS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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