Unit Tests and Coverage for Xamarin using Azure DevOps

Testing is important part of the release management process. Having Unit tests is great advantage for your app from many aspects. From keeping code like documentation to easily detecting possible bugs after code changes.

Great thing is that Azure DevOps has a build in feature to track these statistics. In this Post I will show you how to create a pipeline in Azure DevOps that will run your Unit tests and checks it’s coverage.

Pre-requirements

One of the biggest advantages creating Xamarin.Forms app is that business logic can be separated from the View. That makes the app easy testable. That kind of code is usually found in your View Models, Services, Repositories.

  • Unit test project written in Visual Studio that is testing your business logic (e.g. ViewModels)

Source code used for this Post: https://github.com/PopovskaS/UITestingDemo

Set up

Navigate to your project in your organization on Azure DevOps and from pipeline choose New Pipeline, to create new pipeline. We are going to use the Classic Editor instead the YAML, so select Use the classic editor. Next thing is to select the source where our project is hosted and the default branch for manual and scheduled builds. Once that is done click on continue. From the templates search and choose Empty pipeline.

Once you find the Empty pipeline click on it and apply.

Build Unit tests

By having this section you will be provided with the Unit Tests statistics that will be shown as a separate tab at the top of your successful build (after pipeline run).

First set up a representative name for your pipeline. Once that is done select Pipeline.

  • Add name for the pipeline in Name field.
  • Set the machine that will build and execute your tests. You can choose Hosted > Azure Pipelines and you will be given a capable machine that can do that for you. Use Private > The name of your pool, to choose capable agent from your private pool of agents. It is really important that Coverage works with .NET Framework on Windows and .NET Core on all supported platforms, so whether it is Hosted or private choose a capable agent according to your project Target framework.
  • Select Get sources and in Clean choose True from the dropdown

Nuget tasks

First thing to do is to add the Nuget tasks used to restore the Nuget packages for our project. Click on the plus icon near Agent job and select NuGet tool installer and then add NuGet to restore the nugets.

  • In the NuGet tool installer you can select the version of the installer.
  • In the NuGet task click on the three dots icon and select the .sln file to restore the nugets for your solution

Test execution

Search from tasks .NET Core you can use this task to build and tests your .Net Core projects.

  • In Display name add representative name for your Task e.g. Execute Tests from UI Test Project
  • In Command select test
  • For Path to project select the path to your project if you have multiple projects with unit tests add them all by separating them with space. Start with $(Build.SourcesDirectory) and locate the project/s e.g. $(Build.SourcesDirectory)/UITestingDemo/DemoUnitTests/DemoUnitTests.csproj
  • In Arguments we will choose the configuration in which we want the tests to be build. In this case Release: –configuration Release. Note that if you want to use this configuration throughout your whole pipeline define a variable with e.g. name BuildConfiguration and value Release and switch your arguments to –configuration $(BuildConfiguration).

Having this set up we can determine now form the pipeline the status for our Tests. In example, how many Passed, Failed etc… But further more to strengthen the stability of our project we can determine how much each of our files are covered with the tests.

Code Coverage

For the code coverage we are going to use Coverlet.

Pre-setup

Add coverlet.collector and coverlet.msbuild packages to your Unit tests project

How It Works

The following explanation is taken from documentation. For how Coverlet Coverage works you can go through whole documentation and apply the things that meet your needs: https://github.com/coverlet-coverage/coverlet

Coverlet generates code coverage information by going through the following process:

Before Tests Run

  • Locates the unit test assembly and selects all the referenced assemblies that have PDBs.
  • Instruments the selected assemblies by inserting code to record sequence point hits to a temporary file.

After Tests Run

  • Restore the original non-instrumented assembly files.
  • Read the recorded hits information from the temporary file.
  • Generate the coverage result from the hits information and write it to a file.

Update Test execution task

Since now we are familiar that coverlet generates coverage file we can update our task with some more changes.

  • Update Arguments with –collect:”XPlat Code Coverage” – to get coverage
  • Since we want to test the code coverage only for specific files- where our business logic, is and ignore the extra files detected by coverlet we are also going to set this extra argument –settings ./UITestingDemo/test.runsettings, Create .runsettings file in your project. For the advanced options check the list of options that are supported by coverlet.
  • Check Publish test results and code coverage
<?xml version="1.0" encoding="utf-8" ?>
<RunSettings>
  <DataCollectionRunSettings>
    <DataCollectors>
      <DataCollector friendlyName="XPlat code coverage">
        <Configuration>
          <Format>json,cobertura,lcov,teamcity,opencover</Format>          
          <Exclude>[Xamarin.Forms.Core]*</Exclude> 
          <ExcludeByFile>
            **/Xamarin.Forms.Core/*.cs, **/UITestingDemo/*, **/obj/**/*</ExcludeByFile>
          <IncludeDirectory>**/UITestingDemo/ViewModels/*.cs</IncludeDirectory>
        </Configuration>
      </DataCollector>
    </DataCollectors>
  </DataCollectionRunSettings>
</RunSettings>

In example, in my test.runsettings I excluded the Xamarin.Forms.Core files and all other files in my projects except the ViewModels since my Unit tests cover only the View Models.

By adding these changes in the Temporary Directory few attachments will be added. From those files we are going to use **/coverage.cobertura.xml, this summary file is containing code coverage statistics.

Updated Test execution task

Publish code coverage

Lets publish our statists from Temporary directory to Azure DevOps. Search for Publish code coverage results task.

  • In Display name add representative name for your task
  • In Code coverage tool choose the tool from which code coverage results are generated.. In this case Cobertura
  • In Summary file detect the **/coverage.cobertura.xml from the temporary directory. If it is the only file in that folder with that signature you can use $(Agent.TempDirectory)/**/coverage.cobertura.xml

By setting all this we are ready to run our pipeline.

Save & Queue.

Automated Xamarin UI Tests using Azure DevOps and App Center

Testing is important part of the release management process. Having automated tests in your app is really great advantage for your app from many aspects. Having automated tests means your app can be tested on many devices and densities at once. Saves time, can be run on multiple platforms in the same time and does not require mandatory human presence at the time of testing. And these are just a small examples from the pool of benefits of having Automated UI Tests for apps.

The really great thing is that Azure DevOps together with the App Center are having this awesome opportunity. In this Post I will show you how to create a pipeline in Azure DevOps that will run your UITests in App Center.

Pre-requirements

Before creating the pipeline there are few things that have to be provided.

  • Application archive file. For Xamarin apps it can be an .ipa file(Xamarin.iOS) or .apk/.aab(Xamarin.Android). We are going to use these files to preform the tests on.
  • Xamarin.UITests (or any other Test platform you want to use that is supported on AppCenter) project where the app test are written. From this project we are going to create the test assemblies so we can preform the tests on the archive files.
  • App Center and Azure DevOps subscription. To run the tests on those services.

Setup

Navigate to your project in your organization on Azure DevOps and from pipeline choose New Pipeline, to create new pipeline. We are going to use the Classic Editor instead the YAML, so select Use the classic editor. Next thing is to select the set the source where our project is hosted and the default branch for manual and scheduled builds. Once that is done click on continue. From the templates search and choose Empty pipeline.

If you want to put the Creation of the archives files and Running the UITests phases in the same pipeline select Xamarin.iOS or Xamarin.Android templates. There you will find the tasks for App Center.

Once you find the Empty pipeline click on it and apply.

UI Tests Pipeline creation

First set up a representative name for your pipeline. Once that is done select Pipeline.

  • Add name for the pipeline in Name field.
  • Set the machine that will build and execute your tests. You can choose Hosted > Azure Pipelines and you will be given a capable machine that can do that for you. Of choose Private > The name of your pool, to choose capable agent from your private pool of agents. If you are going to use the pipeline for the iOS app choose Mac machine else for Android you can go with Windows or Mac Machine.

By clicking on Agent job in the Agent pool we can see that <inherit from pipeline> is selected by default. That means that for this phase we will use the set up from the pipeline done in the steps above.

Nuget tasks

First thing to do is to add the Nuget tasks used to restore the Nuget packages for our project. Click on the plus icon near Agent job and select NuGet tool installer and then add NuGet to restore the nugets.

  • In the NuGet tool installer you can select the version of the installer.
  • In the NuGet task click on the three dots icon and select the .sln file to restore the nugets for your solution

Download archive file

If we are having a pipeline that builds our app, where we can find .ipa file and/or .apk/.aab file we will have to select from the tasks Download build artifacts. If you are not sure how to create these pipelines you can check the following links:

For Xamarin.Android – .aab file – Android App Bundle for Xamarin.Android apps using Azure DevOps and App Center

For Xamarin.iOS – .ipa file – Build Xamarin iOS app with Azure DevOps

  • Select Specific build radio button and two extra fields will appear
  • In the Project choose the project that contains the archive.
  • And in Pipeline choose the pipeline that contains the archive
  • In the Build version to download choose the Latest or a specific build you want to use for the testing
  • For Default type choose Specific type radio button and in the Matching pattern type **/NameOfYourArcive.extensionOfYourArchive or if that is the only file with .ipa or .aab/.apk extension you can use this matching pattern **/*.ipa or **/*.aab or **/*.apk
  • Destination directory is where your file will be downloaded so we are fine with the Artifact directory which is preselected by default

Build UITests and create built test assemblies

From the tasks add MSBuild task. This task will be used to build the Xamarin.UITest(or any other type of project supported on App Center) project and create the build test assemblies. Once that is selected do the following setup.

  • In the Project click on the three dots and select the Xamarin.UITests project: YourProject.csproj file
  • For MSBuild version and MSBuild architecture set up the configuration of the MSBuild.
  • For Configuration set the configuration in which you expect the UITests to be used, Debug, Release (Alpha, Beta, Prod – as custom created environments) . In the variables create variable with name BuildConfiguration and value e.g. Release. In Configuration field put $(BuildConfiguration)
  • In the MSBuild Arguments set the path where you want your test assemblies to be placed. We will use this path in the next task. That can be done something like this /p:OutputPath=$(build.binariesdirectory)/uitests. Setting this way the they will be added in the binaries(b) > uitests folder

Create connection with App Center

Now since we have the archive file and the test assemblies we will need to provide them to the App Center to see out tests running there. For that from the tasks select App Center test and do the following setup.

  • In the Binary application file path set the path of your archive. Because in the steps above(section: Download archive file) we set the Artifacts directory the path will look something like this: $(System.ArtifactsDirectory)/drop/NameOfYourArcive.extensionOfYourArchive
  • In the Prepare tests section for Test Framework choose the framework your tests are written with e.g. Xamarin UI Tests.
  • In the Build directory choose Path to directory with built test assemblies, in the previous step we explained where are we going to put the build assemblies and for this pipeline the path will look like this $(build.binariesdirectory)/uitests
  • If you have Store password, Key alias or Key password set them in the Variables lock them and use them safely in your pipeline. For this one we will left that empty.
  • In the Test tools directory locate the path of the test-cloud.exe file. This file is located in your Packages > Xamarin.UITests nuget file (if you are using Xamarin UI Tests) > version of the nuget package > tools the path will look something like this /.nuget/packages/xamarin.uitest/3.0.7/tools

Next is to set the Run Tests section

  • In the Authentication method set we can choose App Center service connection or Credentials. The main difference between these two is Service Connection can be used in multiple pipelines if permitted and Credentials are just for this specific pipeline.
    • If you choose Service connection. Click on Manage and click on New service connection. Search for Visual Studio App Center and then Next. In the Api Token put the token generated under your project in App Center(appcenter.ms) > Account Settings (click on account top right) > Scroll to Api Token > Add new token. Copy the generated Api Token and paste it in this field. In Service connection name add representative name for your connection. Click Save and get back to the pipeline. Click on the Refresh button and the connection will appear.
    • If you choose credentials in the App Center username add your App Center username and for App Center password the password for your App Center account. It is important to create these as locked variables so anyone with the access of the pipeline can not see our credentials.
  • Set the App slug field. The App slug is combination of your username and the app identifier. Go to App Center(appcenter.ms) > Select your project. From the url copy {username}/apps/{yourProjectName} and remove /apps. The App slug should look something like this stefanijapopovska/UITestingDemo
  • In the Devices set the previously created Device set in the App Center(App Center > Test(from menu) > Device sets). This is something similar as the App slug. It should look something like this {username}/{nameOfYourDeviceSet} e.g. stefanijapopovska/UITestingDemoDeviceSet
  • In dSYM directory you can choose the path for iOS symbols

Save & Queue.

Release Xamarin iOS app with Azure DevOps

Releasing an iOS App means that you have already created the executable file and is correctly signed. So it is ready to be deployed to the App Store.

If you are not sure how to preform that you can follow these two links: 

Create release

Initial creation 

  • From the Pipelines in the menu choose Releases, on the plus button click New and New release pipeline 
  • Select start with an empty job and add name to the stage e.g. Deploy app to Test Flight and close the dialog the updated name will be auto saved
  • On the + Add an artifact from the build source choose the definition that builds your app and contains the ipa file in the artifacts 
  • All other fields will be preselected, if you want to make more additional changes change the available options.
  • Click add 

Create Task that builds your app

  • On stages select the stage that deploys your app and in the tasks click on the plus button 
  • From the Tasks search for “Apple app Store release” and choose the task named like that 

Note: In the Agent Job > Agent specification you should choose Mac OS or choose your Mac as self hosted agent 

Lets get back to the task

– Authentication method

There are two options to authenticate with App Store. The Service connection and Username and Password. The main difference between these two is that Service connection can be used between other pipelines (if access granted).

User Name and Password

  • In the email field enter your email and in the password enter the password from your Apple Store account, if you are using two steps verification check the field and type those credentials as well.

Service connection 

  • Click on manage and you will be navigated to Service connections in Settings 
  • Click on new service connection and choose Apple App Store than Next
    • In App Store email add your email address form App Store 
    • In Password your personal password from App Store
    • In the Service connection name add meaningful name for your connection

Since other fields are optional you can just click save and the connection will be created for you 

  • Navigate back to the release task and click on the refresh button next to the Service connection field and the connection will appear.

– Bundle ID

In the Bundle ID enter your unique identifier. That can be find in the info.plist from your Xamarin.iOS project the string form CFBundleIdentifier key.

– Binary Path

In the Binary Path choose the .ipa file from the drop, that file is the one from the artifacts that was created from the previous successful build definition.

Save and Create a release.

Build Xamarin iOS app with Azure DevOps

In this post will be shown how to create Xamarin.iOS Build definition that will build your Xamarin iOS app and also create an archive file. The .ipa file later on can be deployed to the Test Flight and App Store. On how to deploy the app to the store can be seen in the following link: Release Xamarin iOS app with Azure DevOps

Before any app can be successfully distributed to the App Store there are some steps that needs to be done so the app will be recognized and verified from the Apple. That means that that every app has to have a Certificate and a Provisioning Profile valid files. For how to create those, check the following link: Create distribution certificates and provisioning profiles for your iOS app.

Create build definition

  • Navigate to your Azure DevOps account.
  • Form the Pipelines choose Pipelines and New pipeline 
  • Click on Use the classic editor to create a pipeline without YAML.
  • Select your source. Than find the project you want to build
  • Select the default branch and click continue 
  • From templates search for Xamarin.iOS and select the “Xamarin.iOS” task than apply
  • The definition will be created for you

Lets do some modifications

  • In the Pipeline > Agent Pool select the machine that will build your app “Hosted” if you want to use agent assigned to you or “Private” if you want to use your device as agent.
  • Enable “Install an Apple certificate” and “Install an Apple provisioning profile” tasks
  • Also remove “Select Xamarin SDK version“, “Copy Files to: $(build.artifactstagingdirectory)”, “Test with Visual Studio App Center”, “Deploy **/*.ipa to Visual Studio App Center” because we won’t need them for now.

Add the Provisioning profile and the .p12 files in Library

  • Choose from Pipeline > Library Library is used for keeping secure files 
  • Choose Secure files and click on the +Secure files
  • Click on browse and add these two files separately 

Build Pipeline

Install Apple certificate

  • Choose the .p12 file from the secure files in Certificate (P12) field 
  • Navigate in the Variables from the menu and in P12password field as value add your p12 password also lock it so it wont be visible.

Install Apple provisioning profile

  • Choose the Provisioning profile from secure files in the Provisioning profile field 

Xamarin.iOS 

  • Unselect the “Build for iOS Simulator” checkbox
  • In the Signing & Provisioning in Signing identity enter $(APPLE_CERTIFICATE_SIGNING_IDENTITY). This variable is automatically set by the Install Apple Certificate task for the certificate you selected
  • In the Signing & Provisioning in Provisioning profile UUID enter $(APPLE_PROV_PROFILE_UUID). This variable is automatically set by the Install Apple Provisioning Profile task for the provisioning profile you selected.
  • Open the Advanced drop down list and in Arguments enter /p:IpaPackageDir=”$(Build.ArtifactStagingDirectory)”. With this set up the created .ipa file will be stored in the artifacts directory. 

All other fields and tasks can be left as default 

Save & Queue.

Create distribution certificates and provisioning profiles for your iOS app

Every iOS app to be deployed to the App Store must first be identified by Apple. In this post will be shown how to generate those files so later on they can be used for the app deployment to the Store.

Navigate on https://developer.apple.com/account/resources/certificates/list. There are three important sections here that needs to be defined. 

  • Identifiers
  • Certificates
  • Profiles

Identifiers

In this section we can register an unique identifier for our iOS app that will represent the app on App Store. There are no two applications on the App Store with the same identifier. They are usually created as com.yourbrandname.ios. This one must be the same as the string form CFBundleIdentifier key in the iOS project inside the info.plist file.

  • Choose Identifiers from the menu
  • Click on the plus icon next to Identifiers and select App IDs for registering individual app for App Store than continue 
  • From the select type select App and continue 
  • In the Bundle Id choose Explicit and type your Bundle Id e.g. com.yourbrandname.ios and in the description type something recognisable for example “You App Name Bundle Identifier”, continue
  • Review and Register
  • The identifier will be created in the identifiers

Certificates

Certificates are used to sign your app. That helps Apple to identify our organization when submitting to App Store. Certificates includes private and public key. The certificate represents the public key and from that you can export the private key. The Apple does not keep information about the private key. .p12 file that will be exported in the next steps stores certificate together with the private key that corresponds to this certificate.

Create certificate signing request 

The following six steps are taken directly form the apple documentation https://help.apple.com/developer-account/#/devbfa00fef7

  • Launch Keychain Access located in /Applications/Utilities
  • Choose Keychain Access > Certificate Assistant > Request a Certificate from a Certificate Authority.
  • In the Certificate Assistant dialog, enter an email address in the User Email Address field.
  • In the Common Name field, enter a name for the key (for example, Gita Kumar Dev Key).
  • Leave the CA Email Address field empty.
  • Choose “Saved to disk”, and click Continue.

Create certificate

  • Navigate back to the Developer Apple site
  • Choose Certificates from the menu
  • Click on the plus button next to Certificates and select Apple Distribution than continue
  • Choose the created file form the Desktop (the one in the Create certificate signing request section)
  • Click continue 
  • The certificate will be created for you click on download and download it locally 

Generate the .p12 file 

  • Once the file is downloaded locally double click on the certificate
  • Certificate will be automatically added in your Keychain Access
  • If you have multiple certificates from the same account and you are not sure what is your new created certificate in the Keychain Access form the menu choose Keychain Access > Certificate Assistant > Evaluate Certificate and select the downloaded certificate(.cer) remember the Id
  • Go back in the Keychain and choose My certificates. 
  • Find the certificate with the same ID and click on the arrow 
  • The file with key image will appear, right click on it and click Export “…” 
  • Add meaningful name of the p12 file than save 
  • Add password and store it somewhere secure
  • The .p12 file will be downloaded in the disc 

Provisioning Profiles 

Provisioning profiles are used to confirm if the app can be installed in the particular Apple device. The provisioning profile contains information about the signing which means it first must be approved by Apple and the application identity

  • Choose profiles from the menu 
  • Click on the plus button near the Profiles and from Distribution select App Store than continue 
  • Choose App Id created in Identifiers and Continue 
  • Select the certificate created in Certificates and continue
  • Add meaningful name for Provisioning Profile and Generate 
  • Download and double click if you want to export it in your Keychain Access

Register an App

Navigate to https://appstoreconnect.apple.com/apps and click on the plus button near Apps header. New app form will open 

  • Choose iOS or if you support any other platforms
  • In the name add name for your app 
  • Choose primary language in the primary language field
  • In the bundle id choose the bundle id created in the Identifier in the section one 
  • For SKU you should add some unique ID for your app that is not visible for the App Store since bundle id is unique I suggest adding the bundle id as SKU
  • In limited access choose who you want to give access to. 
  • Click create and the app will be created for you

Following these steps you have everything you need for the app deployment to Test Flight/App Store.

Note: This files are used for distributing the app not for development.

What are you going to use is the Provisioning Profile file and the .p12 file with its password.

Xamarin.iOS application branding using Github and App Center

Github & App center opportunities by making the release processes easier

The combination of these two powerful services has made the deployment for mobile application easier than ever, from building to testing and releasing in just few steps. I was amazed how repetitive process can be simplified and automated without any trouble for the developers. That is why I found it challenging and decided to create this article. 

What are we going to do next?

For this article I choose to make modification on the application that was already on my repository for a while, you can look at this article for more details. The aim of the application is to represent just one place in Postcard format. Looking from that point of view there was no point to create so many repositories for the same application and a real time saver would be if the same application is able to be used for various places. I was thinking for a way to reuse the same code, but yet create many more different apps.

Why we need to brand an application?

At the first sight this may looks irrational but there are many use case scenarios where this is required, here are some of them:

  1. You want to sell same product for different companies, but all of them have different name, different logo and colors that are typical for the brand.
  2. You want to create multiple environments from the same application ALPHA, BETA, PRODUCTION they all are the same app but may work with another API.

Lets get started

Lets navigate to App Center, and choose Build section from the Menu. Choose to edit already existing build definition. In the built configuration there is a section called Build scripts. By using this section we can execute custom scripts in our application. As in the official documentation is stated three type of scripts are available: post-clone, pre-build and post-build. The post-clone script runs immediately after the repository was cloned but before we do anything else on our end. The pre-build script runs before the actual build starts. The post-build script runs after the build has finished and we have copied all the necessary artifacts to the output directory.

Where to create these scripts?

Create these scripts in the same folder where your .sln file is, or .cspoj file if Android. Note that there is already a collection of scripts that you may find useful.

The full code which I am modifying can be find on Github. It is an Xamarin.iOS application referring to the above script changes are in appcenter-pre-build.sh file.

Firstly lets take a look of the essential configuration information changes, that are under info.plist file.

  •  Change App Name – Changing application name identifies what name users will see (from Postcard Zagreb -> Postcard Bitola)
plutil -replace CFBundleName -string "$BUNDLE_NAME" $APPCENTER_SOURCE_DIRECTORY/StretchyHeader.iOS/Info.plist
  • Change the App bundle identifier – Changing the identifier means that we are creating unique application for any of the brands. (com.stretchyheader.ios.zagreb -> com.stretchyheader.ios.bitola) 
plutil -replace CFBundleIdentifier -string $BUNDLE_IDENTIFIER $APPCENTER_SOURCE_DIRECTORY/StretchyHeader.iOS/Info.plist
  • Change version name – Changing version name means that our branded app does not need to be the same version name(1.0, 2.0 …) as the original app
plutil -replace CFBundleShortVersionString -string $BUNDLE_VERSION $APPCENTER_SOURCE_DIRECTORY/StretchyHeader.iOS/Info.plist

Further more the changes should be applied as well as on the constants for the application. This usually includes Colors, Translations specific for application, Rules that tend to be modified during different scenario’s, variables that are used in the entire application.

Note: While developing application we have to be sure that we always use the same resource. e.g.: Using class for color palette, not hardcoding the colors. 

In this case Branding folder files are what is going to be modified.

  • Change color palette – Static content where Colors are placed
find $APPCENTER_SOURCE_DIRECTORY/StretchyHeader.iOS/Branding/ColorPalette.cs -type f -exec sed -i '' "s/PrimaryColor\ =\ \(.*\)/PrimaryColor\ =\ UIColor.FromRGB($PRIMARY_COLOR_R,$PRIMARY_COLOR_G,$PRIMARY_COLOR_B);/g" {} \;
	
find $APPCENTER_SOURCE_DIRECTORY/StretchyHeader.iOS/Branding/ColorPalette.cs -type f -exec sed -i '' "s/AccentColor\ =\ \(.*\)/AccentColor\ =\ UIColor.FromRGB($ACCENT_COLOR_R,$ACCENT_COLOR_G,$ACCENT_COLOR_B);/g" {} \;
  • Change content – Dynamic data can be really complex to modify, for that purpose I chose a powerful command plutil as helper
plutil -convert xml1 $APPCENTER_SOURCE_DIRECTORY/StretchyHeader.iOS/Branding/AppResources.json -o $APPCENTER_SOURCE_DIRECTORY/StretchyHeader.iOS/Branding/AppResources.plist
	

plutil -replace headerImage -string $POSTCARD_HEADER_IMAGE $APPCENTER_SOURCE_DIRECTORY/StretchyHeader.iOS/Branding/AppResources.plist
	

plutil -replace title -string $POSTCARD_NAME $APPCENTER_SOURCE_DIRECTORY/StretchyHeader.iOS/Branding/AppResources.plist
	

plutil -replace description -string "$POSTCARD_DESCRIPTION" $APPCENTER_SOURCE_DIRECTORY/StretchyHeader.iOS/Branding/AppResources.plist
	

plutil -convert json -o $APPCENTER_SOURCE_DIRECTORY/StretchyHeader.iOS/Branding/AppResources.json  $APPCENTER_SOURCE_DIRECTORY/StretchyHeader.iOS/Branding/AppResources.plist
	

rm $APPCENTER_SOURCE_DIRECTORY/StretchyHeader.iOS/Branding/AppResources.plist 

In many of the scripts you may noticed that the environment variables are marked with $. Same as in the code, we do not hard code the values because we want to use the scripts for many postcards. Here is where we define the variables.

After applying all this changes and rebuilding the app from App Center, here is the product we get. A brand new postcard 🙂

Postcard place using Stretchy header in Xamarin.iOS

A year ago I have published a public repository on how to create a stretchy header using Xamarin.iOS application. This time I decided to go a little further with this solution, by preparing it to be able to be rebranded. For now let’s look at how to implement this sassy component.

The app is postcard for places and for this demo I chose to write about my favorite city by far. Full implementation can be previewed on (Github Code).

Create TableView with source and delegate separate

Lets start with creating a TableView and presenting the data with TableViewDataSource and TableViewDelegate by providing custom cells that will contain the Header and the Content for the postcard. The Heather is chosen to be a photo and the Content is information, both about the city.

TableViewDataSource

public class TableViewDataSource : UITableViewDataSource
{
    private const int numberOfRowsInSection = 1;

    public override UITableViewCell GetCell(UITableView tableView, 
                                            NSIndexPath indexPath)
    {
         return tableView.DequeueReusableCell(TableViewContentCell.Key) as
                               TableViewContentCell ?? 
                               TableViewContentCell.Create();
    }

    public override nint RowsInSection(UITableView tableView,
                                       nint section) 
    {
         return numberOfRowsInSection;
    }
}

TableViewDelegate

public class TableViewDelegate : UITableViewDelegate
{
   public override nfloat EstimatedHeight(UITableView tableView,
                                          Foundation.NSIndexPath indexPath)
   {
        return tableView.RowHeight;
   }

   public override nfloat GetHeightForRow(UITableView tableView, 
                                          Foundation.NSIndexPath indexPath)
   {
        return UITableView.AutomaticDimension;
   }
}

Add the Stretchy Header to the TableView

To achieve a transparency at the top of the screen the header has to be added as subview in the TableView. By doing this, the header goes above the content in the TableView and in this scenario they are overlapping. Next step is to push down the content by managing the ContentOffset and ContentInset of the TableView. In this demo the Header size is 300. After these steps proper position of the elements in the view is achieved.

TableView.DataSource = new TableViewDataSource();


TableView.Delegate = new TableViewDelegate();


tableViewHeaderCell = TableView.DequeueReusableHeaderFooterView(
                                 TableViewHeaderCell.Key) 
                                 as TableViewHeaderCell ?? 
                                 TableViewHeaderCell.Create();

tableViewHeaderCell.UserInteractionEnabled = true;

TableView.TableHeaderView = null;


TableView.AddSubview(tableViewHeaderCell);

To achieve the effect of stretching the header rectangle, a variable called effectiveHeight have to be created. The effectiveHeight is fixed size which is the initial height of the header. What is important for the header is to change the content mode to Aspect Fill so it will scale the content to fit the size of itself by changing the aspect ratio of the content.

The following code represents the ability of the header to adapt its size to the changes done while scrolling. As proposed the header is the default size when there is no movement. By scrolling, the header height is supposed to increase by adding the difference between the offset and the effectiveHeight. If the absolute value of the offset is smaller than the effectiveHeight no changes will occur. And simple as that we are done with the logic.

public void UpdateHeaderView()
{
    var headerRect = new CGRect(0,
                                -effectiveHeight,
                                UIScreen.MainScreen.Bounds.Width,
                                TABLE_HEADER_VIEW_HEIGHT);

    if (TableView.ContentOffset.Y < -effectiveHeight)
    {
           headerRect.Y = TableView.ContentOffset.Y;
           headerRect.Height = (-TableView.ContentOffset.Y);
    }

    tableViewHeaderCell.Frame = headerRect;
       
 }

Invoke method in scroll event of the TableView

As said above, this happens on scrolling the TableView. So, next create an event which will listen to table scroll and invoke it on the Scrolled method in the TableViewDelegate, than attach it on the TableView.

public delegate void DidScrollEventHandler();

public event DidScrollEventHandler DidTableScrolled;

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

MainTableViewController

tableViewDelegate.DidTableScrolled += UpdateHeaderView;

After doing all these steps, what is left is to compile, run your app, and watch this beautiful effect happening.