[ShinobiEssentials] [Accordion Control] iOS Xamarin - Adding Content to a Section via a List


#1

Hello Shinobi, I’ve been demoing your Component from the Xamarin Component Store and had a question concerning dynamic accordion menu creation. Below you can find a code snippet of what I was attempting to create so that I could pass multiple Content (as UIVIew) elements to be “children” of the parent section. However, when I try to run the following, I get an error within the foreach logic, indicating: “An element with the same key already exists in the dictionary.” I’ve been modifying the “Hello World” example, as you can see from some of the content which I am trying to add.

My questions:

  1. How should I proceed to make a generic method that will be able to accept any number of UIView elements which can be added as child elements to the accordion?
  2. Can accordion menus be nested within a parent accordion menu? (Unrelated to my topic, but that could be useful.)

public SEssentialsAccordionSection CreateSection(RectangleF rectangle, string name, List<UIView> content)
        {
            SEssentialsAccordionSection section = new SEssentialsAccordionSection (rectangle, name);                    

            foreach(UIView cont in content)
            {                
                       mapSectionToView.Add (section, cont);
            }
            return section;
        }


// create the content that shows when the section is opened
            UILabel content = new UILabel (new RectangleF(0, 0, View.Bounds.Width, 30)) {
                Text = “Add some content here”,
                TextAlignment = UITextAlignment.Left
            };
            
            UILabel moarContent = new UILabel (new RectangleF(0, 0, View.Bounds.Width, 30)) {
                Text = “Add MOAR content here”,
                TextAlignment = UITextAlignment.Left
            };
            List<UIView> contentArray = new List<UIView>();
            contentArray.Add(content);
            contentArray.Add(moarContent);

            for (int i = 0; i < 3; i++) {
                accordion.AddSection(dataSource.CreateSection(new RectangleF(0, 0, View.Bounds.Width, 50), 
                                                                                                    "Hello World " + i, contentArray));

            }


#2

Another question I had while I was looking into ShihnobiEssentials: How can one manage delegates/events for content items? 

Let me explain my objective. I wish to have an accordion menu that contains nested accordion menus which have content items that allow the user to select a particular item which will reload a webpage in a WebView. These Content items (as well as the sections) should have the ability to be color coded to represent their current status. The Content, being that it is a UIView element, I can imainge that I would be able to use a custom designed UIView template that represents the items that I want to display such as a label and image.

Below is an example of the structure which I am attempting to design:


#3

I was thinking more on how I could have multiple elements within a Section and tried to utilize a UITableView with a custom set of elements (which could be dynamically generated) that the user would be able to see. However, when I tried to create a test case for this, I was unable to see the list of UILabels that I had created within the Section of the Accordion. 

Test Code: (within the CreateSection() method)

            UITableView tableUI = new UITableView(rectangle);
            tableUI.Editing = false;
            tableUI.Add(new UILabel(){ Text = “Test” });
            tableUI.Add(new UILabel(){ Text = “World” });
            tableUI.Add(new UILabel(){ Text = “Info” });
            tableUI.Add(new UILabel(){ Text = “Hello” });
            mapSectionToView.Add(section, tableUI);

I’m starting to run short on ideas on how I can utilize this Component as needed. Any assistance or insights would be greatly appreciated!


#4

Hi Joshua_D,

Thank you for your query. With regards to your first question I believe the issue is when you are trying to add to your Dictionary in your CreateSection method. The error message is a good clue here “An element with the same key already exists in the dictionary.” - you are creating a new section and trying to use it as the key for both of your content views (UILabels) in the content List. A Dictionary must have unique keys, but here you are trying to create a Dictionary that maps 1 key to 2 values.

Putting aside the issue with the Dictionary, the accordian’s datasource requests 1 content view per section (via GetContent), so the technique of trying to return/map several individual UILabels per section won’t work. A solution to this would be to setup one content view per section, where each content view had your 2 UILabels added to them. You could then return and map 1 content view for each section.

There is another issue in the code in your first question which will need to be resolved. You are creating only 2 UILabels. These same UILabels are being passed to each created section - this likely won’t work in the way you expect (you’ll end up seeing only 2 labels if you get the app to run). The solution to this will be to create fresh UILabels per created section/content view.

I hope that information is useful and helps you with your first query!

With regards to your second post - I’d like to mention that although it is probably possible to nest our accordian control to achieve the tree-like UI you are after, I can see this code becoming quite complex and difficult to maintain due to the number of accordians you’ll need to manage. This might be something worth considering when assessing the Accordian’s suitability for your problem. However, this should be possible by nesting multiple Accordians.

Loading webpages when your content is tapped is probably best handled by making your content responsible for responding to user input (either through use of Apple’s own UIControls or by adding gesture recognisers to them).

Hope all that info helps!  :laughing:

Ryan


#5

Hi Joshua_D,

I’d missed your third post when writing my response - sorry about that!

You’ll need to debug your code to find out why your UITableView isn’t being shown. Things you can try that might help reveal the issue are:

  1. Testing what is returned from your GetContent method (i.e. is it a UITableView with the correct frame and content?)
  2. Testing that your UITableView renders correctly independent of the Accordian. The issue might not actually be the accordian, so it would be good to rule our this issue. You should be able to do this by getting rid of the accordian from your code and just adding the UITableView as a subview to your view controller’s view.

Let us know how you get on with this problem and we’ll try and work through it with you.

Regards

Ryan


#6

Ryan, thanks for your input. I’ve been trying to abstract out functionality based off the Hello World example and have come up with the following. Though at current, I can only get UILabel elements to display within the Section. Here’s a code snippet from my ViewController class thus far:

            SEssentialsSlidingOverlay slidingView = new SEssentialsSlidingOverlay (View.Frame, true);
            View.AddSubview (slidingView);

            slidingView.Overlay.AddSubview (webView);

            AccordionDataSource dataSource = new AccordionDataSource ();

            UIView menuView = new UIView(new RectangleF(0, 70, 200, 1000));

            SEssentialsAccordion accordion = new SEssentialsAccordion (menuView.Bounds) {
                DataSource = dataSource
            };

            UILabel content = new UILabel (new RectangleF(0, 0, View.Bounds.Width, 30)) {
                Text = “Test Label”,
                TextAlignment = UITextAlignment.Left,
            };
            UIButton button = new UIButton();
            button.SetTitle(“Some Button”, UIControlState.Normal);
            button.TouchUpInside += (object sender, EventArgs e) =>    
                { 
                    Console.WriteLine(“Menu Item selected!”);
                };

            UIView newView = new UIView();
            newView.Add(content);
            newView.Add(button);
            newView.Frame = new RectangleF(0, 0, View.Bounds.Width, 200);

            for (int i = 0; i < 3; i++) {
                accordion.AddSection(dataSource.CreateSection(new RectangleF(0, 0, View.Bounds.Width, 50),
"Hello World " + i, button));
}

            menuView.Add(accordion);
            slidingView.Underlay.AddSubview (menuView);

--------------------------------------- (Separate AccordionDataSource class)

    public class AccordionDataSource : SEssentialsAccordionDataSource
    {
        Dictionary<SEssentialsAccordionSection, UIView> mapSectionToView = 
                     new Dictionary<SEssentialsAccordionSection, UIView>();

        public SEssentialsAccordionSection CreateSection(RectangleF rectangle, string name, UIView content)
        {
            SEssentialsAccordionSection section = new SEssentialsAccordionSection (rectangle, name);                    
            mapSectionToView.Add(section, content);

            return section;
        }

        public override UIView GetContent (SEssentialsAccordion accordion, SEssentialsAccordionSection section)
        {
            return mapSectionToView [section];
        }
    }

You can see that I have a test scenario here, with the expectations that I have three sections that each one will contain a UIView containing a UILabel and a UIButton (and they will be duplicated in this example).  I have set the AccordionDataSource class to accept a UIView parameter, which should be added when the Section itself is created. This would give me control to customize the UIView to whatever I wish on the main ViewController side. However, the only success I have had thus far in using the UIView element is the UILabel displaying, even when the UIButton (or a second UILabel) is included in the pased UIView. I can never see anything out of the UIView outside of the first UILabel. With that said, I have tried to pass a UIButton element directly (since UIButton inherits UIView), however this does not render the button within the Section either.

I can’t seem to find a reason as to why the Accordion would not render partial components of the passed-in UIView object. Do you (or others) have any suggestions? 

EDIT: I just had the idea that perhaps the fact that there would exist the same exact button (with the same event handler), that there would be an issue with rendering the UIButtons in that sense. To test, I removed the iteration (the for block) and had the AddSection get called only one time, so that I should only have one Section returned with one set of UI elements. This was still not enough to get the UIButton element visible within the Section.


#7

Hi Joshua_D,

Your comment in the “EDIT” section of your last post is definitely something that you need to look into. This is the same thing I was referencing in my original post when I stated “There is another issue in the code in your first question which will need to be resolved. You are creating only 2 UILabels. These same UILabels are being passed to each created section - this likely won’t work in the way you expect (you’ll end up seeing only 2 labels if you get the app to run). The solution to this will be to create fresh UILabels per created section/content view.”

UIViews can only exist in one place at a time. If you try to add them to several superviews (such as accordion sections) then they’ll just appear in the last superview they were added to. Basically each section will need to show its own distinct UIViews/UIButtons. 

Let us know how you’ve got on once you’ve addressed this issue. :slight_smile:


#8

I was able to get it working. I created a new method for adding a button which would be given a set of parameters such as the title and navigation URL. I left-justified each button's text inside of my custom method.

This led me to one more question: Can I left-justify the text for the section title? How would I go about setting that property?

 MainMenuViewElements mainMenuItems = new MainMenuViewElements();

UIButton button = mainMenuItems.AddButton(menuView, this, “Google”, “http://www.google.com/”, 0);
UIButton anotherBtn = mainMenuItems.AddButton(menuView, this, “Xamarin”, “http://www.xamarin.com/”, 30);

UIView firstSectionView = new UIView();
firstSectionView.AddSubview(button);
firstSectionView.AddSubview(anotherBtn);
firstSectionView.Frame = new RectangleF(0, 0, View.Bounds.Width, 100);

accordion.AddSection(dataSource.CreateSection(new RectangleF(0, 0, View.Bounds.Width, 50), “Links”, firstSectionView));

        // - - - - - - - -   MainMenuViewElement__ Method
        public UIButton AddButton(UIView parent, WebViewController webViewController, string btnText, 
                                   string URL, float yVal)
        {
            UIButton button = new UIButton (new RectangleF (0, yVal, parent.Bounds.Width, 30)){
                BackgroundColor = UIColor.LightGray,
            };
            button.SetTitle ("  " + btnText, UIControlState.Normal);
            button.SetTitleColor (UIColor.Red, UIControlState.Highlighted);
            button.HorizontalAlignment = UIControlContentHorizontalAlignment.Left;
            button.TouchUpInside += (sender, e) =>
                {
                    webViewController.URL = URL;
                    webViewController.webView.LoadRequest(new NSUrlRequest (
                                                                    new NSUrl(webViewController.URL)));
                };

            return button;
        }


#9

Hi Joshua_D,

It’s great that you managed to resolve your issue! Would you mind starting another forum thread with your new question? We can then mark it as resolved. It also has the added benefit of keeping the each individual thread on a single topic which helps keep things tidy and helps us get answers out more quickly!  :laughing:

Thanks,
Jan Akerman