Auto Layout, Beer Drinkin, iOS, Uncategorized

Stretchy UITableView Headers with Xamarin

The Yahoo News Digest app includes a couple of interesting user interface elements that I wanted to use within my own apps. The feature that I was most keen to recreate was the stretching UITableViewHeader. Its an effect seen in lots of iOS (sometimes referred to as a parallax header). As Beer Drinkin is going to support multi-tasking on iOS, I needed to ensure my implementation continues to support Storyboards and Auto Layout. Fortunately it proved very simple to get everything setup. In this blog post I’ll be sharing how I went about implementing it

beerdrinkinStretchy

Setting up the Storyboard

Adding a header view

To get started I opened my existing Storyboard and selected the UIViewController that requires the tableview header. In my case the scene (or view controller) isn’t a UITableViewController because I require a floating ‘Check in’ button to be visible at all times. Its worth noting that all the steps in the tutorial work with both UITableViewControllers and UIViewControllers.

Screen Shot 2016-02-01 at 11.36.06

Once I had the storyboard loaded, I dragged a UIView from the toolbox and made sure to insert it above the UITableViewCells as a the header view for the table. I then added a UIImageView to the new UIView and set its constraints to be 0,0,0,0. This way when the parent view (the UIView) resizes, the UIImageView will resize as well. I also made sure to set the UIImageView view mode property to Aspect Fit, which makes sure the image looks great no matter what size the view.

Screen Shot 2016-02-01 at 11.39.13

Adding some C#

Adding the resize code

If I were to have run this now, the table header would be displayed but wouldn’t resize with scroll events. To add scaling, I needed to add a code into my ViewController to setup the stretchy goodness that I wanted.

Because I use the header views height in a number of locations throughout the beer description view controller, I went ahead and created a variable rather than scattering magic numbers over my class.

private nfloat headerViewHeight = 200;

Managing the header view

To allow me to manage the table header, I needed to remove it from the UITableView and keep it as a variable for use later. To do this I created a variable in the beer description view controller.

private UIView headerView;

When we load the view controller, we’ll want to set our headerView variable and then set the UITableViews header property to null. This means the tableview has no header view to manage anymore, instead I’ve taken control of the view which allows me to ensure it resizes correctly as the table view scrolls.Despite having just removed the header view from the UITableView, I actually want to go ahead and add it to the table view hierarchy (but not as the header view property of the UITableView)

 headerView = tableView.TableHeaderView;
 tableView.TableHeaderView = null;
 tableView.AddSubview (headerView);
 tableView.ContentInset = new UIEdgeInsets (headerViewHeight, 0, 0, 0);
 tableView.BackgroundColor = UIColor.Clear;

Listening to TableViewDidScroll

In order to successfully respond to the DidScroll event of the UITableViewSource, I’ll need to create an event in the table views delegate. This is because of an issue with the UITableView DidScroll event not firing when a delegate has been set.

public override void Scrolled (UIScrollView scrollView)
{
    DidScroll ();
}

public event DidScrollEventHandler DidScroll;

We can now hook up the table DidScroll event with a small piece of logic for resizing the view.

//Update Tableview
tableView.Source = new BeerDescriptionDataSource(ref cells);
var deleg = new DescriptionDelegate (ref cells);
deleg.DidScroll += UpdateHeaderView;
tableView.Delegate = deleg;

tableView.ReloadData ();
View.SetNeedsDisplay ();
//...
void UpdateHeaderView ()
{
    var headerRect = new CGRect (0, -headerViewHeight, tableView.Frame.Width, headerViewHeight);
    if (tableView.ContentOffset.Y < -headerViewHeight)
    {
        headerRect.Location = new CGPoint (headerRect.Location.X, tableView.ContentOffset.Y);
        headerRect.Size = new CGSize (headerRect.Size.Width, -tableView.ContentOffset.Y);
    }
    headerView.Frame = headerRect;
}

Conclusion

Its very easy to use this approach to add resizing animations to any number of controls within your UITableView. My favourite part of this solution is that it works perfectly across all iOS devices and doesn’t force me to drop support of Autolayout.

Azure, Beer Drinkin

Creating a 5 star search experience

Search is a feature that can make or break your mobile app, but it can be incredibly difficult to get right. In this blog post, I’m going to share how I’m solving search with Beer Drinkin.

There are many options for us developers looking to implement search solutions into our projects. Some of us may decide to use Linq and Entity Framework to look through a table, and the more adventurous may opt to create an instance of Elastic Search, which requires a lot of work to set up and maintain. For Beer Drinkin, I’m using Microsoft’s Azure Search service as it has proved to be easy to configure and requires zero maintenance.

The reason that Beer Drinkin uses Azure Search is simple: the BreweryDB search functionality is too limited for my needs. One example of this is that the end point often returns zero results if the user misspells a search term. If I searched for “Duval” rather than “Duvel,” BreweryDB’s search would return zero beers. Even if I were to spell the search term correctly, BreweryDB would return all beers from the Duvel Moortgat brewery. Although this is minor, I would prefer that Maredsous 6 and Vedett Extra White not be returned as these do not have “Duvel” in the name.

Screen Shot 2015-12-21 at 15.36.14

Spelling Mistakes

Another issue with using the default search functionality of BreweryDB is its inability to deal with spelling mistakes or offer suggestions. Simple off-by-one-letter spelling mistakes yield no results, something that should be easy to resolve.

Screen Shot 2015-12-21 at 15.40.40

I’ve had sleepless nights worrying that on release, users fail to find results due to simple spelling mistakes. One way to address spelling mistakes is to utilize a spell checking service like WebSpellChecker.net.

The issue with a service such as WebSpellChecker is that it has no context in which to make corrections when it comes to names of products and it also doesn’t support multiple languages.

Another way to minimize spelling mistakes is to provide a list of suggestions as the user types in a search query. You’re probably familiar with this in search engines like Google and Bing. This approach to searching is intuitive to users and significantly reduces the number of spelling mistakes.

Enter Azure Search

Azure Search aims to remove the complexity of providing advanced search functionality by offering a service that does the heavy lifting for implementing a modern and feature-rich search solution. Microsoft handles all the infrastructure required to scale as it gains more users and indexes more data. Not to mention that Azure Search supports 50 languages, which use technologies from multiple teams within Microsoft (such as Office and Bing). What this equates to is Azure Search understands the languages and words of the search requests.

Some of my favorite features

Fuzzy search – Find strings that match a pattern approximately.

Proximity search – Geospatial queries. Find search targets within a certain distance of a particular point.

Term boosting –  Boosting allows me to promote results based on rules I create. One example might be to boost old stock or discounted items.

Getting Started

The first step I took was to provision an Azure Search service within the Azure Portal. I had two options for setting up the service; I could have opted for a free tier or have paid for dedicated resources. The free tier offers up to 10,000 documents and 50MB storage, which is a little limited for what I need.

Because my index already contains over 50,000 beers, I had no option but to opt for the Standard S1 service, which comes at a cool $250 per month (for Europeans, that’s €211). With the fee comes a lot more power with the use of dedicated resources, and I’m able to store 25GB of data. When paying for Search, you’ll be able to scale out to 36 units, which provides plenty of room to grow.

Creating an index

Before I could take advantage of Azure Search, I needed to upload my data to be indexed. Fortunately, with the .NET SDK the Azure Search team provides, it’s exceptionally easy to interact with the service. Using the .NET library I wrote a few weeks ago, which calls BreweryDB, I was able to iterate quickly through each page of beer results and upload them in blocks to the search service.

Screen Shot 2016-01-04 at 10.18.02.png

Uploading documents

Parallel.For(1, totalPageCount, new ParallelOptions {MaxDegreeOfParallelism = 25}, index =>
{
    var response = client.Beers.GetAll(index).Result;
    var beersToAdd = new List();
    foreach (var beer in response.Data)
    {
        var indexedBeer = new IndexedBeer
        {
            Id = beer.Id,
            Name = beer.Name,
            Description = beer.Description,
            BreweryDbId = beer.Id,
            BreweryId = beer?.Breweries?.FirstOrDefault()?.Id,
            BreweryName = beer?.Breweries?.FirstOrDefault()?.Name,
            AvailableId = beer.AvailableId.ToString(),
            GlassId = beer.GlasswareId.ToString(),
            Abv = beer.Abv
         };

         if (beer.Labels != null)
         {
            indexedBeer.Images = new[] {beer.Labels.Icon, beer.Labels.Medium, beer.Labels.Large};
         }
         beersToAdd.Add(indexedBeer);
    }
    processedPageCount++;
    indexClient.Documents.Index(IndexBatch.Create(beersToAdd.ToArray().Select(IndexAction.Create)));

    Console.Write( $"\rAdded {beersToAdd.Count} beers to Index | Page {processedPageCount} of {totalPageCount}");
});

Other data import methods

Azure Search also supports the ability to index data stored in Azure SQL or DocumentDB, which enables you to point a crawler to your SQL table and ensures it is always up to date rather than requiring you to manually manage the document index yourself. There are a few reasons you may not want to use a crawler. The best reason for not using a crawler is that it introduces the possibility of a delay between your DB changing and your search index reflecting the changes. The crawler will only crawl on a schedule, which results in an out-of-data index.

If you opt for the self-managed approach, you can add, remove, and edit your indexed documents yourself as the changes happen in your back end. This provides you with live search results as you know the data is always up to date. Using the crawler is an excellent way to get started with search and quickly get some data in place, but I wouldn’t consider it a good strategy for long-term use.

I mentioned earlier that the free tier is limited to 10,000 documents, which translates to 10,000 rows in a table. If your table has more than 10,000 rows, then you’ll need to purchase the Standard S1 tier.

Suggestions

Before we can use suggestions, we’ll need to ensure that we’ve created a suggester within Azure.

Screen Shot 2016-01-04 at 10.27.15.png

In the current service release, there is support for limited index schema updates. Any schema updates that would require re-indexing, such as changing field types, are not currently supported. Although existing fields cannot be modified or deleted, new fields can be added to an existing index at any time.

If you’ve not checked the suggester checkbox at the time of creating a field, then you’ll need to create a secondary field as Azure Search doesn’t currently support editing the fields. The Azure Search team recommends that you create new fields if you require a change in functionality.

The simplest way to get suggestions would use the following API.

var response = await indexClient.Documents.SuggestAsync(searchBar.Text, "nameSuggester");
foreach(var r in response)
{
    Console.WriteLine(r.Text);
}

Having fun with the suggestion API

The API suggestion provides properties for enabling fuzzing matching and hit highlighting. Let’s see how we might enable that functionality within our app.

var suggestParameters = new SuggestParameters();
suggestParameters.UseFuzzyMatching = true;
suggestParameters.Top = 25;
suggestParameters.HighlightPreTag = "[";
suggestParameters.HighlightPostTag = "]";
suggestParameters.MinimumCoverage = 100;

What do the properties do?

UseFuzzyMatching – The query will find suggestions even if there’s a substituted or missing character in the search text. While this provides a better search experiance, it comes at the cost of slower operations and consumes more resources.

Top – Number of suggestions to retreive. It must be a number between 1 and 100, with its default to to 5.

HightlightPreTag – Gets or sets the tag that is prepended to hit highlights. It MUST be set with a post tag.

HightlightPostTag – Gets or sets the tag that is prepended to hit highlights. It MUST be set with a pre tag.

MinimumCoverage – Represents the precentage of the index that must be covered by a suggestion query in order for the query to be reported a sucess. The default is 80%.

How do the results look?

Simulator Screen Shot 4 Jan 2016, 10.39.25.png

Search

The Search API itself is even easier (assuming we don’t use filtering, which is a topic for another day).


var searchParameters = new SearchParameters() { SearchMode = SearchMode.All };

indexClient.Documents.SearchAsync(searchBar.Text, searchParameters);

Special Thanks

I’d like to take a moment to thank Janusz Lembicz for helping me get started with Azure Search suggestions by answering all my questions. I  appreciate your support (especially given it was on a weekend!).

Beer Drinkin, Libraries

Updated BreweryDB .NET client

687474703a2f2f7777772e6272657765727964622e636f6d2f696d672f62616467652e706e67

header.png


Over the weekend I decided to look at a project which hasn’t recieved much love since I orginally wrote it earlier in the year. That project is PCL for interacting with the awesome beer database that is BreweryDB.

My orginal implementation was very simplistic in its design, only exposed a handful of the endpoint, lacked any error checking and also had a few too little unit tests. With BeerDrinkin’s development coming along nicey, I thought it time to take a look at some of its dependencies and see how I could improve them. First up was BreweryDB and so I set about improving the PCL to hopefully make it useful for other .NET developers.

Supported enpoints

This new version of BreweryDB includes many more supported endpoints. Its now possible to query almost all of BreweryDB using .NET.

  • Adjuncts
  • Beers
  • Breweries
  • Categories
  • Events
  • Features
  • Fermentables
  • FluidSizes
  • Guilds
  • SocialSites
  • Yeasts

 

Getting Started

Beers

  • GET/beers
  • GET/beer/beerId
  • GET/search/

Get all

This returns a list of all beers but will be paginated with 50 beers per page.

//Will return the first page
var response = await client.Beers.GetAll();

//Will return the fourth page
var response = await client.Beers.GetAll(4);

Get by id

This returns a single beer

var response = awaitclient.Beers.Get("cBLTUw");

Using request parameters

This returns a  list of beers

var parameters = new NameValueCollection {{BeerRequestParameters.Name, "duvel single"}};
var response = await client.Beers.Get(parameters);

Search by name

This returns a  list of beers

var response = await client.Beers.Search("duvel");

Breweries

  • GET/breweries
  • GET/brewery/breweryId
  • GET/search/

Get all

This returns a list of all breweries but will be paginated with 50 breweries per page.

//Will return the first page
var response = await client.Breweries.GetAll();

//Will return the forth page
var response = await client.Breweries.GetAll(4);

Get by Id

This returns a single Brewery

var response = await client.Breweries.Get("YXDiJk");

Search by name

This returns a  list of breweries

var response = await client.Breweries.Search("duvel");

Naturally its open source

As always, I’ve made this avaiaible on GitHub and Nuget for you to use in your own apps.