April 12th, 2017

Displaying Data with macOS Table Views

In addition to beautiful, cross-platform native mobile apps, Xamarin also enables you to create macOS apps with Xamarin.Mac. To get started, be sure to read our introductory post to macOS development in C# where we begin to build a Pomodoro task-tracking application. Our application consists of a countdown timer that assists with managing one’s workload into 25 minute blocks.

Displaying a list of data is a common pattern for any type of application development, from mobile to desktop. Table views are great for displaying structured data and are backed with powerful APIs, which allow the table view to be manipulated. Across all mobile development platforms, the basic concept is the same; a list view contains cells, which are populated by a code behind class that populates the list and tells it how to behave. If you’re already familiar with iOS development, the macOS approach will feel even more similar. In this post, we’ll be building on our Pomodoro macOS app by adding an activity log to track accomplishments by displaying them in a table view.

Mar-29-2017 14-34-54

Adding a Table View to the UI

To follow along, download the starter code for this blog post, which contains the basic Pomodoro macOS app we’ll be extending. The first thing we’ll do is update our user interface to add the table view. In Xamarin Studio, double-click the storyboard file to open in Xcode.

Move the timer label and start button to the top of the window to make room for the table view. Next, open the Object Library on the right side, and scroll down to find the Table View. Drag it onto the window and adjust as necessary to fit. To change the table headers for each column, double-click the header text and enter a new title, such as “Time Completed” and “Task”.

We can make this list easier on the eyes by alternating the background color for each list item. To do so, select the Table View from the View Controller Scene and check the Alternating Rows option from the Attributes Inspector, as shown here:

Screen Shot 2017-03-31 at 17.34.14

Populating the Table View

Now that we’ve added the table view itself, we need to populate it with tasks. If you’ve added a table view to an iOS (or Android) project before, this will look familiar to you.

The first thing we need to do is create a new class, which will store the information we want to hold for each row in the table view. Create a new empty class named Activity.cs (right-click the macOS project -> New File… -> General -> Empty Class) and add the following code:

using System;


namespace Pomodoro

{

    public class Activity
    {
        public DateTime TimeCompleted { get; set; }
        public string TaskDescription { get; set; }

        public Activity() { }


        public Activity(DateTime timeCompleted, string taskDescription)

        {
            this.TimeCompleted = timeCompleted;
            this.TaskDescription = taskDescription;
        }
    }
}


To manage the data in the table view we need to use a NSTableViewDataSource which holds a list of Activity. Also, override GetRowCount to return the number of records in the list. Add another new empty class named ActivityLogDataSource.cs, and add the following code.

using System;

using System.Collections.Generic;

using AppKit;


namespace Pomodoro

{
    public class ActivityLogDataSource : NSTableViewDataSource

    {
        public ActivityLogDataSource()
 { }

        public List Activities = new List();

        public override nint GetRowCount(NSTableView tableView)
        {
            return Activities.Count;
        }
    }
}

We need a delegate in addition to the data source, which will return the view for a given column and row. The view will be populated with data from the data source we created above. Add a new empty class named ActivityLogDelegate.cs.

using System;

using System.Collections.Generic;

using AppKit;


namespace Pomodoro

{
    public class ActivityLogDelegate : NSTableViewDelegate

    {
        private const string CellIdentifier = "ActivityCell";
        private ActivityLogDataSource DataSource;

        public ActivityLogDelegate(ActivityLogDataSource datasource)
        {
            this.DataSource = datasource;
        }

        public override NSView GetViewForItem(NSTableView tableView, NSTableColumn tableColumn, nint row)
        {
            // This pattern allows you reuse existing views when they are no-longer in use.
            // If the returned view is null, you instance up a new view.
            // If a non-null view is returned, you modify it enough to reflect the new data.
            NSTextField view = (NSTextField)tableView.MakeView(CellIdentifier, this);
            if (view == null)
            {
                view = new NSTextField();
                view.Identifier = CellIdentifier;
                view.BackgroundColor = NSColor.Clear;
                view.Bordered = false;
                view.Selectable = false;
                view.Editable = false;
            }

            // Set up view based on the column and row
            switch (tableColumn.Title)
            {
                case "Time Completed":

                    view.StringValue = DataSource.Activities[(int)row].TimeCompleted.ToString("H:mm");

                    break;
                case "Task":
                    view.StringValue = DataSource.Activities[(int)row].TaskDescription;
                    break;
            }

            return view;
        }
    }
}


Connecting Everything

To be able to reference the table view from the view controller, we’ll need to create an outlet. Open the Assistant Editor view in Xcode and control-drag from the “Table View” in the document outline (not directly from the storyboard) onto the ViewController.h code. Name this “ActivityLog”. If you’re unsure how to do this, check out the previous blog post where we touched on creating outlets and actions from the storyboard in Xcode.

Mar-31-2017 19-46-21

Back in Xamarin Studio, open ViewController.cs and add the following property:

ActivityLogDataSource log = new ActivityLogDataSource();

Then, in the ViewDidLoad method, we need to set the delegate and datasource for the table view:

ActivityLog.DataSource = log;
ActivityLog.Delegate = new ActivityLogDelegate(log);

Finally, we want to provide the user with a way to input their task once the timer has finished. The easiest way to do this would be to add a text field to the alert sheet. We need to handle the text from this input and add it to the activity log along with the current time. Directly after the code for setting up the alert, add the following snippet:

using CoreGraphics;
...

// The text field we are going to add to the sheet
var input = new NSTextField(new CGRect(0, 0, 300, 20));
// Add the text field to the sheet
alert.AccessoryView = input;

// Get the current time
.
DateTime CurrentTime = DateTime.Now;
// Display the NSAlert from the current view

alert.BeginSheetForResponse(View.Window, (result) =>

{
    // When the sheet is dismissed add the activity to the log
    log.Activities.Add(new Activity(CurrentTime, input.StringValue));
	ActivityLog.ReloadData();

});

We’re finished! In just a few lines of code we added a macOS NSTableView that allows us to keep a record of our tasks completed in Pomodoro cycles.

Mar-29-2017 14-34-54

Wrapping Up

Table views in macOS are just as powerful as table views in iOS and equally simple to use in your apps. If you want to learn more about table views in Xamarin.Mac then check out our documentation. The starting point and completed project can be found at https://github.com/BytesGuy/XMPomodoro/

Category
Developers
Topics
iOS

Author

0 comments

Discussion are closed.