Skip to content

Instantly share code, notes, and snippets.

@MilenPavlov
Last active July 19, 2017 09:14
Show Gist options
  • Save MilenPavlov/a73a401d6299c02cc329 to your computer and use it in GitHub Desktop.
Save MilenPavlov/a73a401d6299c02cc329 to your computer and use it in GitHub Desktop.
Xamarin iOS – Create custom TreeView control for iPad / iPhone
This example uses Storyboards and iPad app. We have few entities here the TreeViewController (also holding the TreeViewSource delegate and my TreeNode Class) and TreeViewCell where I’m adding UIViews programatically.
Code as follows:
1) TreeViewController class:
public partial class TvViewController : UIViewController
{
public TvViewController(IntPtr handle) : base(handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
TableView.Source = new TreeViewSource(TableView, ScrollView);
}
}
public class TreeViewSource : UITableViewSource
{
const string Input = @"{'title': 'Root Folder', 'type':'folder','filepath':'', 'children' :[{'title': 'Subfolder1','type':'folder','filepath':'', 'children' :[{'title': 'Subsubfolder','type':'folder','filepath':'', 'children' :[{'title': 'This file name is very ... very long .... !!!', 'type':'file','filepath':'','children' :[]}]},{'title': 'Empty','type':'folder', 'filepath':'','children' :[]}]},{'title': 'Subfolder2','type':'folder','filepath':'', 'children' :[]}]}";
private readonly UITableView _tableView;
private readonly UIScrollView _scrollView;
public List<TreeNode> Nodes;
public TreeViewSource(UITableView tableView,UIScrollView scrollView)
{
_tableView = tableView;
_scrollView = scrollView;
Nodes = new List<TreeNode>();
var jsonInput = JObject.Parse(Input);
var a = ConvertJSONInput(null, jsonInput, 0);
Nodes.Add(a);
}
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
var node = Nodes[indexPath.Row];
var cell = tableView.DequeueReusableCell("TreeViewCell") as TreeViewCell;
cell.SetCellContents(node);
return cell;
}
public override nint RowsInSection(UITableView tableview, nint section)
{
return Nodes.Count;
}
public override nfloat GetHeightForRow(UITableView tableView, NSIndexPath indexPath)
{
return 40;
}
private TreeNode ConvertJSONInput(TreeNode parent, JObject input, int depth)
{
var node = new TreeNode
{
NodeName = input["title"].ToString(),
NodeType = input["type"].ToString(),
FilePath = input["filepath"].ToString(),
NodeLevel = depth + 1,
Parent = parent
};
foreach (var c in input["children"])
{
node.Children.Add(ConvertJSONInput(node, JObject.Parse(c.ToString()), node.NodeLevel));
}
return node;
}
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
tableView.DeselectRow(indexPath, true);
var node = Nodes[indexPath.Row];
var children = new List<TreeNode>();
node.IsExpanded = true;
if (node.Children.Any())
{
children.AddRange(node.Children);
var isInserted = false;
foreach (int index in children.Select(child => Nodes.IndexOf(child)))
{
isInserted = (index > 0 && index != int.MaxValue);
if (isInserted)
{
break;
}
}
var arrayCells = new List<NSIndexPath>();
if (isInserted)
{
ColapseRow(tableView, children);
tableView.ReloadData();
}
else
{
var count = indexPath.Row + 1;
foreach (var treeNode in children)
{
treeNode.IsExpanded = false;
//if there's Parent's children not selected remove them
var nodesToRemove = new List<TreeNode>();
if (treeNode.Parent != null)
{
if (treeNode.Parent.Children.Any() && treeNode.Parent.Parent != null)
{
nodesToRemove.AddRange(treeNode.Parent.Parent.Children.Where(x=>!x.IsExpanded));
if (nodesToRemove.Any())
{
ColapseRow(tableView, nodesToRemove);
}
}
}
arrayCells.Add(NSIndexPath.FromRowSection(count, 0));
Nodes.Insert(count++, treeNode);
}
tableView.InsertRows(arrayCells.ToArray(), UITableViewRowAnimation.None);
tableView.ReloadData();
}
}
}
private void ColapseRow(UITableView tableView, List<TreeNode> children)
{
foreach (var child in children.Distinct())
{
int indexToRemove = Nodes.IndexOf(child);
if (child.Children != null && child.Children.Any())
{
ColapseRow(tableView, child.Children);
}
if (Nodes.Contains(child))
{
Nodes.Remove(child);
NSIndexPath[] indexArray = {NSIndexPath.FromRowSection(indexToRemove, 0)};
tableView.DeleteRows(indexArray, UITableViewRowAnimation.None);
}
child.IsExpanded = false;
}
}
}
public class TreeNode
{
public string NodeName { get; set; }
public string NodeType { get; set; }
public int NodeLevel { get; set; }
public string FilePath { get; set; }
public List<TreeNode> Children { get; set; }
public TreeNode Parent { get; set; }
public bool IsExpanded { get; set; }
public TreeNode()
{
Children = new List<TreeNode>();
}
}
2) TreeViewCell class:
partial class TreeViewCell : UITableViewCell
{
private UIImageView _imageView;
private UILabel _titleLabel;
public string TitleValue = "";
public TreeViewCell (IntPtr handle) : base (handle)
{
}
public int Level;
public TreeViewCell(UITableViewCellStyle style, string reuseIdentifier) : base(style, reuseIdentifier)
{
}
public void SetCellContents(TreeNode node)
{
Level = node.NodeLevel;
_imageView = new UIImageView
{
Image = (node.NodeType == "folder") ? UIImage.FromFile("folder.png") : UIImage.FromFile("file.png"),
ContentMode = UIViewContentMode.Left
};
_imageView.SizeToFit();
_titleLabel = new UILabel()
{
TextColor = UIColor.Black,
BackgroundColor = UIColor.Clear,
Text = node.NodeName
};
_titleLabel.SizeToFit();
//important: remove all previous subviews before adding the new ones!
foreach (var subview in ContentView.Subviews)
{
subview.RemoveFromSuperview();
}
ContentView.AddSubviews(_imageView, _titleLabel);
}
public override void LayoutSubviews()
{
base.LayoutSubviews();
var contentRect = ContentView.Bounds;
var boundsX = contentRect.X;
int LevelIndent = 40, CellHeight = 40;
var imageFrame = new RectangleF(((float)boundsX + Level - 1) * LevelIndent, 0, (LevelIndent), CellHeight);
_imageView.Frame = imageFrame;
var titleFrame = new RectangleF(((float)boundsX + Level - 1) * LevelIndent + (LevelIndent) + 2, 0, (float)_titleLabel.Bounds.Width, CellHeight);
_titleLabel.Frame = titleFrame;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment