Xamarin iOS – Create custom TreeView control for iPad / iPhone

Posted on Updated on

custom_treeview_snippet

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.

Sample GitHub repo can be found here: https://github.com/MilenPavlov/TreeViewSample (horible design) enjoy 🙂

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': 'Subfolder','type':'folder','filepath':'', 'children' :[{'title': 'Subsubfolder','type':'folder','filepath':'', 'children' :[{'title': 'This file name is very ... very long .... !!!', 'type':'file','filepath':'when click file, go to another controller','children' :[]}]},{'title': 'Empty','type':'folder', 'filepath':'','children' :[]}]},{'title': 'Subfolder','type':'folder','filepath':'', 'children' :[]}]}";
   private readonly UITableView _tableView;
   private readonly UIScrollView _scrollView;
   public List 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 25;
   }

   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();
      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();
          if (isInserted)
          {
            ColapseRow(tableView, children);
            tableView.ReloadData();
          }        
          else
          {
               var count = indexPath.Row + 1;

               foreach (var treeNode in children)
               {
                  arrayCells.Add(NSIndexPath.FromRowSection(count, 0));
                  Nodes.Insert(count++, treeNode);
               }

               tableView.InsertRows(arrayCells.ToArray(), UITableViewRowAnimation.Fade);

              tableView.ReloadData();
          }
      }
   }

   private void ColapseRow(UITableView tableView, List children)
   {
       foreach (var child in children)
       {
           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.Fade);
           }
       }
   }
}

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 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();
         _imageView.Frame = new RectangleF((float)_imageView.Frame.X, (float)_imageView.Frame.Y, (float)_imageView.Bounds.Width, (float)_imageView.Bounds.Height);

        _titleLabel = new UILabel()
        {
            TextColor = UIColor.Black,
            BackgroundColor = UIColor.Clear,
            Text = node.NodeName
        };
        _titleLabel.SizeToFit();
        _titleLabel.Frame = new RectangleF((float)_titleLabel.Frame.X, (float)_titleLabel.Frame.Y, (float)_titleLabel.Bounds.Width, (float)_titleLabel.Bounds.Height);

        ContentView.AddSubviews(_imageView, _titleLabel);
    }

    public override void LayoutSubviews()
    {
        base.LayoutSubviews();

        var contentRect = ContentView.Bounds;
        var boundsX = contentRect.X;
        int LevelIndent = 25, CellHeight = 25;

        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, (float)_titleLabel.Bounds.Height);
       _titleLabel.Frame = titleFrame;
   }
}
Advertisements

9 thoughts on “Xamarin iOS – Create custom TreeView control for iPad / iPhone

    Matt said:
    September 14, 2015 at 4:03 pm

    Could you post the whole project solution of this please there are some things which are not working even after some configuring, such as the list having a type etc. Thanks in advance

    Like

      milenppavlov responded:
      September 14, 2015 at 4:08 pm

      Hi Matt, I have to recreate the example, will try to do that with in the next few days, together with github repo with the code 🙂

      Like

    Matt said:
    September 16, 2015 at 12:49 pm

    Hi Milen, I did manage to get everything working, xamarin a lot of times wants a full clean and build not sure if that was it, but anyway most of the list stuff required public List Nodes; Nice example thank you very much 🙂

    Like

      Matt said:
      September 16, 2015 at 12:51 pm

      Funny I think wordpress removed the brackets so I guess it was not the code as I see in my comments it disappeared as well 🙂 public List “” Nodes;

      Like

      milenppavlov responded:
      September 16, 2015 at 5:18 pm

      Hi Matt, thanks for you comments, I will also post sample solution code soon (I’m quite busy these last few days)

      Like

    Matt said:
    September 16, 2015 at 1:09 pm

    Hmm there is a trick for code in wordpress and escaped html characters

    public List Children { get; set; }
    

    Like

    Matt said:
    September 16, 2015 at 1:19 pm

    Hmm this is a newer wordpress plugin that might help with the issue https://bg.wordpress.org/plugins/dont-break-the-code/

    Like

    Matt said:
    September 16, 2015 at 3:20 pm

    Everything in between the greater than less than symbol got deleted by wordpress in the above code and also in the comments like “TreeNode” so the type of the lists went away sorry to be redundant

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s