Month: February 2019

Xamarin iOS UITableView GetCell Method Called for Invisible Cells

Xamarin iOS UITableView GetCell Method Called for Invisible Cells

In my Xamarin iOS application I’m leveraging the UITableView control to page through a collection of items in a performant manner. The UITableView uses the GetCell method of the UITableViewDataSource delegate to create or reuse instances of cells as they become visible on screen.

Great, that sounds efficient, and it is! However, in my application the GetCell method was being called once for every cell when the UITableView was initialized, including the invisible ones.

What gives?

This post spawned from a stackoverflow question I posted, then immediately found the answer to.

Replicating the Behavior By Example

The UITableView in question was added to a ViewController in the StoryBoard. Within the ViewController’s ViewDidLoad method, I have the following setup:

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

    var model = new UIColor[] {
        UIColor.Green,
        UIColor.Red,
        UIColor.Magenta,
        UIColor.Cyan,
        UIColor.Blue,
        UIColor.Purple
    };

    SampleTableView.RowHeight = SampleTableView.Frame.Height;
    SampleTableView.Setup(model);
}

In short, a model consisting of a collection of colors is created and passed through to a Setup method on my custom UITableView, SampleTableView.
In addition to the setup, I already know the height of each row ahead of time, so the RowHeight property of the UITableView is set. As you can see, each row will take the full height of the UITableView. Swiping up or down will then reveal the next page.

Here’s what the SampleTableView and related dependencies look like:

public class SampleCell : UITableViewCell {
    public void Setup(UIColor color) {
        BackgroundColor = color;
    }
}

public class SampleTableViewDataSourceDelegate : UITableViewDataSource {
    public SampleTableViewDataSourceDelegate(UIColor[] model) {
        this.model = model;
    }

    private readonly UIColor[] model;

    public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath) {
        Debug.WriteLine($"GetCell called. Row {indexPath.Row}");
        var reuseIdentifier = "SampleCell";

        var cell = tableView.DequeueReusableCell(reuseIdentifier) as SampleCell;

        cell = (cell ?? (cell = new SampleCell()));

        cell.Setup(model[indexPath.Row]);

        return cell;
    }

    public override nint NumberOfSections(UITableView tableView) {
        return 1;
    }

    public override nint RowsInSection(UITableView tableView, nint section) {
        return model.Length;
    }
}

public partial class SampleTableView : UITableView {
    public SampleTableView(IntPtr handle) : base(handle) {
        PagingEnabled = true;
    }

    private UIColor[] model;

    public void Setup(UIColor[] model) {
        this.model = model;

        DataSource = new SampleTableViewDataSourceDelegate(model);
    }
}

SampleCell
A custom cell that simply sets the background color to the one provided by the model.

SampleTableViewDataSourceDelegate
A derived class of UITableViewDataSource, which handles implementing the GetCell method. This class is responsible creating and reusing cells as they become visible.

SampleTableView
A derived class of UITableView, which handles initial setup of the UITableView via the Setup method, invoked by the containing ViewController.

We now have a fairly slimmed down example of a UITableView implementation. In the GetCell delegate method there’s a line of code that outputs a message to the log when it’s called. After running the application with Visual Studio in a simulator, the Visual Studio Output console shows the following:

[0:] GetCell called. Row 0
[0:] GetCell called. Row 1
[0:] GetCell called. Row 2
[0:] GetCell called. Row 3
[0:] GetCell called. Row 4
[0:] GetCell called. Row 5

Therein lies the issue; the GetCell method is called once for each cell in our model. Why? Didn’t I specify the RowHeight to be the height of the UITableView? If that’s true, and we know that GetCell should only be called when cells become visible, then why is it being called Model.Length number of times?

UITableView EstimatedRowHeight

Our RowHeight property is set to take up the entire UITableView. When the UITableView data source is initialized, however, the UITableView attempts to estimate the required row height for us on-the-fly via the EstimatedRowHeight property. The EstimatedRowHeight property was not set, effectively relying on the table view to calculate it for us. The solution to this particular problem then is to set the EstimateRowHeight property in addition to the RowHeight property.

Back in the ViewDidLoad method of the ViewController, adding the following line of code above the RowHeight setter did the trick:

 SampleTableView.EstimatedRowHeight = SampleTableView.Frame.Height; 

The Output log now shows what we’d expect, a single call to GetCell on initialize:

[0:] GetCell called. Row 0

jQuery Validation Rules for Dynamically Generated Content

jQuery Validation Rules for Dynamically Generated Content

jQuery Validation is a very convenient client-side validation framework that has made the implementation of client-side validation much more streamlined. For many web applications, introducing validation for a static form is as simple as defining the rules, messages and a handful of other (optional) settings. What about when we have dynamic content on our form? How can we define these rules for content we generate dynamically?

Follow along with this JSFiddle, which outlines the scenario described below.

Dynamically Set jQuery Validation Rules

We’ll start with a simple use case. Pretend for a moment you have a form with a few inputs: First Name, Last Name and Age. The jQuery Validation setup might look something like this:

var formValidationSettings = {
  ignore: '[readonly], :hidden',
  highlight: function(element) {
    $(element).addClass('has-error');
  },
  unhighlight: function(element) {
    $(element).removeClass('has-error');
  },
  errorElement: 'span',
  errorClass: 'help-block error',
  errorPlacement: function(error, element) {
    // do nothing, which suppresses the validation message
  },
  rules: {
    FirstName: {
      required: true
    },
    LastName: {
      required: true
    },
    Age: {
      required: true
    }
  }
};

// initialize the form's validator, applying the static form rules
$('form').validate(formValidationSettings);

We defined a few rules stating that each of the three inputs are required, as well as specified how and where our errors should be placed. This is fairly static content, allowing us to setup the jQuery Validation once and forget it. Not bad.

Now let’s bake in the ability for the user filling out the form to define a list of dependents. This will be done by introducing a grid where the user can hit “Add”, be presented a set of additional inputs to fill out, save the record and repeat. When the save button is clicked we want to trigger validation of this dynamic content.

Let’s say the dynamic inputs we present are Dependent Name and Relationship. We could define the rules for these inputs in the original form validation setup. However, that would trigger validation of all the other inputs on the form any time we attempt to save a dependent record. In other words, before we can successfully add a dependent record, all other inputs on the form must also be valid. That isn’t ideal. Instead, let’s define the dynamic input validation rules on the fly.

Fast forward a bit: the user clicked the “Add” button to enter a new dependent. They filled in the Dependent Name but neglected to enter the Relationship. They now attempt to save. Prior to triggering the jQuery Validation (e.g. $form.valid();), we must define our contextual dependent rules:

var dependentValidationSettings = {
  rules: {
    DependentName: {
      required: true
    },
    Relationship: {
      required: true
    }
  }
};

var saveDependent = function() {
  // create a deep copy (not reference) of the original form jQuery Validator settings
  var originalSettings = $.extend(true, {}, $form.validate().settings);

  // overwrite the original form settings with those for the dependent record
  var settings = $form.validate().settings;
  $.extend(settings, dependentValidationSettings);

  if ($form.valid()) {
    // save record
  }

  // reset the original settings after validating the dependent record
  $.extend(settings, originalSettings);
};

Let’s go over what’s happening here: when the “Save” button is clicked and our Save function triggered, we first create a deep copy of the original “form-wide” validation rules and set that aside. We’ll be using this later to revert our changes. Prior to asking jQuery to validate the form we alter the validation settings by overwriting the “rules” property with our dynamic “dependent-only” validation settings. jQuery Validation is now tuned in on the dependent inputs, leaving the rest of the form alone. At the very end of all this we unwind our rule changes and put jQuery Validation back to its original form-wide state.

There we have it, a way to focus the validation rules that jQuery Validation cares about on-the-fly.