0 Comments

Title Image showing a library

When it comes to file handling and Xamarin Forms you can find all you need in the official Documentation. However, when it comes to where the data should be stored the documentation leaves some points open. Moreover, might even lead to, dear I say it, your rejection in the App Store...

Also when writing Cross-Platform Code, with .Net Standard, it does depend on the Operating System (OS) that the app is being executed on, where to store your data. So let's dive into the platforms which are primarily supported by Xamarin Forms.

Xamarin.Essentials

The most comfortable way is using Xamarin.Essentials. Currently still in preview and requires Android 7.1 or higher. However, if that does not make you blink, these are the options:

  • Local Storage which is also backed up.
  • Cache Storage which is, well for caching files but are more on the non-permanent side.
  • Files bundled with the app, i.e. read-only files.

You can access the folder paths as follows:

var rootDirectory = FileSystem.AppDataDirectory;

Currently Xamarin.Essentials supports Android, iOS and UWP. So if all the platforms your app requires are named, and you do not need any other location. Be sure to check out Xamarin.Essentials and check the documentation for more details.

Do it yourself

In most cases the folders offered by Xamarin.Essentials will suffice, but if you require a different folder, i.e. the document folder under iOS, you can always set the path to the location on your own. So let's have a look at how you can achieve this.

Android

Now when it comes to Android, you can follow the documentation from Microsoft and use the following path for storing your files:

_rootDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

Looking to cache some files, then we can take the path and with Path.Combine set the path to the cache folder:

_cacheDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "..", "cache");

If you want to store files to the SD-Card, i.e. external storage under Android. You have to pass in the path from a platform Project to the .Net Standard project or use multitargeting to achieve this. You can get the path as follows:

var sdCardPath = Environment.ExternalStorageDirectory.AbsolutePath;

Note that the Environment here is Android.OS.Environment and not System.Environment. So if Android is this easy how hard can iOS be?

iOS

When storing files under iOS, there is a bit more documentation to read. The reason being that Apple uses multiple subfolders within the Sandbox container. The ones that are important for file storage are the following:

  • Documents: In this folder, only user-created files should be stored. No application data, which includes that JSON file of your app, should be stored here. This folder is backed up automatically.

  • Library: The ideal spot for any application data you do not want the user should have direct access to. This folder is backed up automatically.

    • Library/Preferences: A subdirectory which you should not directly access. Better use the Xamarin.Essentials library for storing any key/value data. This data is backed up automatically.
    • Library/Caches: This is an excellent place to store data which can easily be re-created. This data is not backed up.
  • tmp: Good for temporary files, which you should delete when no longer used.

For a more detailed listing check out the docs. So when storing files, you are under iOS this code will point to the Documents folder:

_rootDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

If you intend to store information into the Library folder you can change the directory as follows:

_rootDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "..", "Library");

Equally, you can change your path to one of the other locations described above.

UWP

UWP apps usually also store their data in a sandbox. Only if in the app's metadata the permission is set and a good reason was given, which Microsofts validates on submission to the store, can the app access other file locations outside of the sandbox. UWP apps also live in a sandbox. The ApplicationData offers to store data locally, roaming or in a temporary location. The local folder can be accessed as follows:

_rootDirectory = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);

Similar the other locations can be accessed for example the roaming folder (which is synced automatically across all of your different devices):

_rootDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "..", "RoamingState");

The following directories are present in a UWP apps sandbox:

UWP Container folders: AC, AppData, LocalCache, LocalState, RoamingState, Settings, SystemAppData, TempState

However, what if I am targeting other platforms?

Since Xamarin Forms is no longer limited to Android, iOS and UWP, you might find yourself wanting to write files on another system such as Tizen. The best solution is to check the documentation of the given platform where data should be stored and then run the following code on the platform:

class Gnabber 
{ 
    // ... 
    private IEnumerable<DirectoryDesc> DirectoryDescriptions() 
    { 
        var specialFolders = Enum.GetValues(typeof(Environment.SpecialFolder)).Cast<Environment.SpecialFolder>(); 
        return specialFolders.Select(s => new DirectoryDesc(s.ToString(), Environment.GetFolderPath(s))).Where(d => !string.IsNullOrEmpty(d.Path)); 
    } 
    // ... 
} 
 
class DirectoryDesc 
{ 
    public DirectoryDesc(string key, string path) 
    { 
        Key = key; 
 
        Path = path == null 
            ? "" 
            : string.Join(System.IO.Path.DirectorySeparatorChar.ToString(), path.Split(System.IO.Path.DirectorySeparatorChar).Select(s => s.Length > 18 ? s.Substring(0, 5) + "..." + s.Substring(s.Length - 8, 8) : s)); 
    } 
 
    public string Key { get; set; } 
    public string Path { get; set; } 
}

The code above lists all used folders from the System.Environment.SpecialFolder for the given environment, and also provides you with the absolute path. If none of the special folders is the target location, you desired. Try using Path.Combine and a path nearby to get to your desired location.

Conclusion

Storing files is not tricky but put some thought into where to store your applications data can go a long way. Xamarin.Essentials may provide all the functionality you need, but if not you usually use the System.Environment.SpecialFolders and the System.Environment.GetFolderPath to get access to different folders offered by the platform you are on.

0 Comments

Image showing a laptop with graphs on it -looking fancy that's all...

Did you know that with Visual Studio 2017 there was an update in the target project files of your Xamarin Projects? They no longer contain a packages.config file but contain the NuGet references directly in the csproj. Using NuGet references instead of the packages.config file has numerous benefits ranging from performance improvements to only showing your top-level dependencies (no longer will you have a scajilion package references ).

They also fix a pesky bug I have experienced since partially migrating to .Net Standard. Migrating to .Net Standard is not something new in the Xamarin World. There are many blog posts out there which will guide you through how to migrate your Portable Class Libraries (PCLs) to .Net Standard. My personal favorite is the approach I first read on James Montemagnos blog, which is pretty straightforward and will allow you to keep your version history.

But depending on when you have created your project you will start running into compile errors after the migration telling you that a dll from a NuGet which you have only referenced in the .Net Standard project(s) can not be found in the output folder. As is the case in our sample app which I have created for this blog post:

Showing compile error that ReactiveUI dll was not found in app output folder

In the sample project, we are referencing the ReactiveUI NuGet only in our .Net Standard code and therefore do not have a direct NuGet reference in our platform projects. When looking closely at the platform projects we can see that the Android and iOS project are using a packages.config file to reference their NuGet packages.

One solution you will find on the internet is to add these NuGet packages to your target projects. While this works it has got that yucky feel to it. Furthermore, this can lead to quite a bit of bloat since some projects come with sub-dependencies which will all show up in your NuGet package manager. But there is an easier way which is also less nauseous

Migrating to the newer Package Reference style can be done by simply right clicking on your packages.config and selecting Migrate packages.config to PackageReference...:

Visual Studio dialog showing Migrate To PackageReference

This action will change the csproj from this:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Import Project="..\..\packages\Xamarin.Forms.3.1.0.637273\build\netstandard2.0\Xamarin.Forms.props" Condition="Exists('..\..\packages\Xamarin.Forms.3.1.0.637273\build\netstandard2.0\Xamarin.Forms.props')" />
  <!-- File includes and that stuff -->
  <ItemGroup>
    <Reference Include="System" />
    <Reference Include="System.Xml" />
    <Reference Include="System.Core" />
    <Reference Include="Xamarin.Forms.Core, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
      <HintPath>..\..\packages\Xamarin.Forms.3.1.0.637273\lib\Xamarin.iOS10\Xamarin.Forms.Core.dll</HintPath>
    </Reference>
    <Reference Include="Xamarin.Forms.Platform, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
      <HintPath>..\..\packages\Xamarin.Forms.3.1.0.637273\lib\Xamarin.iOS10\Xamarin.Forms.Platform.dll</HintPath>
    </Reference>
    <Reference Include="Xamarin.Forms.Platform.iOS, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
      <HintPath>..\..\packages\Xamarin.Forms.3.1.0.637273\lib\Xamarin.iOS10\Xamarin.Forms.Platform.iOS.dll</HintPath>
    </Reference>
    <Reference Include="Xamarin.Forms.Xaml, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
      <HintPath>..\..\packages\Xamarin.Forms.3.1.0.637273\lib\Xamarin.iOS10\Xamarin.Forms.Xaml.dll</HintPath>
    </Reference>
    <Reference Include="Xamarin.iOS" />
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\HelloReactiveUI\HelloNetStandard.csproj">
      <Project>{984433BA-6DB2-4606-8AB1-E5E070C60D44}</Project>
      <Name>HelloNetStandard</Name>
    </ProjectReference>
  </ItemGroup>
  <Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
  <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
    <PropertyGroup>
      <ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
    </PropertyGroup>
    <Error Condition="!Exists('..\..\packages\Xamarin.Forms.3.1.0.637273\build\netstandard2.0\Xamarin.Forms.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Xamarin.Forms.3.1.0.637273\build\netstandard2.0\Xamarin.Forms.props'))" />
    <Error Condition="!Exists('..\..\packages\Xamarin.Forms.3.1.0.637273\build\netstandard2.0\Xamarin.Forms.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Xamarin.Forms.3.1.0.637273\build\netstandard2.0\Xamarin.Forms.targets'))" />
  </Target>
  <Import Project="..\..\packages\Xamarin.Forms.3.1.0.637273\build\netstandard2.0\Xamarin.Forms.targets" Condition="Exists('..\..\packages\Xamarin.Forms.3.1.0.637273\build\netstandard2.0\Xamarin.Forms.targets')" />
</Project>

To this:

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <!-- File includes and that stuff -->
  <ItemGroup>
    <Reference Include="System" />
    <Reference Include="System.Xml" />
    <Reference Include="System.Core" />
    <Reference Include="Xamarin.iOS" />
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\HelloReactiveUI\HelloNetStandard.csproj">
      <Project>{984433BA-6DB2-4606-8AB1-E5E070C60D44}</Project>
      <Name>HelloNetStandard</Name>
    </ProjectReference>
  </ItemGroup>
  <ItemGroup>
    <PackageReference Include="Xamarin.Forms">
      <Version>3.1.0.637273</Version>
    </PackageReference>
  </ItemGroup>
  <Import Project="$(MSBuildExtensionsPath)\Xamarin\iOS\Xamarin.iOS.CSharp.targets" />
</Project>

Which only references the top level package. Further the package.config file will be removed. Since this method is still in development you might run into some bumps. So be sure to check out the limitations which might apply to the packages you are currently using. If that is the case make sure to let the team know. But once you have migrated, you will be able to use your project without the compile issue and all the benefits from using Nuget package references.

Conclusion

In this post, you saw how to migrate your Xamarin Projects to use package references. Using package references is intended to be the new adding NuGet package references directly to your csproj. With this migration, an error introduced when migrating your PCL projects to .Net Standard will also be solved. So be sure to check out this option when updating your Xamarin apps that have been out in the wild for a while.

Note: that all new projects created with Visual Studio 2017 will automatically use the package reference approach.

To see the sample app in full you can check them out on GitHub packages.config and package reference.

Thank you to Pierce Bogganwho pointed me in the right direction to get this problem solved.

0 Comments

Image with red dots - intended to look fancy

Some apps require quite a bit of content which is fairly static but changes over time and then the app should adjust and provide the user with the new content. Let's assume we want an app that provides us with quotes and their authors. We could just add the quotes to our app but whenever we wanted to update the app we would have to redeploy our app to the store(s). This can range from an inconvenience to requiring technical expertise for updating the app for simply correcting such a simple thing as a comma. So it becomes evident that in these cases we would like to separate the content from the app itself.

Hosting content does not require running any logic on the server. We do not need any other service than that of a simple file share. With perhaps one or two additional requirements regarding security etc. but more on that later. And that is exactly what Azure Blob Storage can provide us with.

Setting up the blob storage

You are required to have an Azure Account to create a blob storage, the steps, therefore, you can find here. On Azure create a blob storage, under containers, create a Container if you haven't done so already and then upload your data to it. In this sample, we will upload a single JSON file.

Showing Blobstorage Container with one JSON File

In a real application, we could also provide multiple other files including videos and other static files. But for this simple demo, we will stick to a lonely JSON file. We can access the content by calling the URL:

Sample get request with Postman

Having something on a public server always raises questions about security and the sorts. So let's have a look at them.

Security

So let's start with what you get out of the box. The easiest security is if your data is public. Like on a website but for your app. The default you can limit the anonymous access to your blob storage as follows:

  • read-only container: which will allow everyone to read at the container level i.e. "look at the directory" and list all the blobs within.
  • read-only blob: here the caller will have to know which blob he wants to open and is limited to reading the blob storage itself.
  • no read access for anonymous: this will restrict the access to authenticated parties only.

In our sample, we will stick with anonymous blob access. But if you are interested in adding some extra layers of security be sure to check out the Azure Storage security guide which explains the different methods of authentication from shared keys all the way to using Azure Active Directory (Azure AD) for securing the access of your data. Which in summary means Blob Storage is not only quick and convenient in the beginning but can also be modified to add some serious layers of protection.

The client

AppInAction

On the client, we will want to consume the hosted resource and use it in our app. You can do this rather simply within a .Net Standard library using JSON.Net as follows:

string quotesJson;
using (var httpClient = new HttpClient())
{
    var response = await httpClient.GetAsync("https://gnabberonlinestorage.blob.core.windows.net/alpha/quotes.json");
    quotesJson = await response.Content.ReadAsStringAsync();
}
_quotes = JsonConvert.DeserializeObject<List<QuoteInfo>>(quotesJson);

While the above sample works and will get us our data it is not really smart, it will always pull the entire file even if nothing has changed. Fortunately, Azure Blob Storage supports ETags which allows us to be smarter when creating the call, by adding the If-None-Match header to our request as follows:

public async Task Init()
{
    if (_quote != null) return;

    IsBusy = true;
    string quotesJson;
    using (var httpClient = new HttpClient())
    {
        if(!string.IsNullOrEmpty(CurrentEtagVersion)) httpClient.DefaultRequestHeaders.Add("If-None-Match", CurrentEtagVersion);
        var response = await httpClient.GetAsync("https://gnabberonlinestorage.blob.core.windows.net/alpha/quotes.json");

        quotesJson = response.StatusCode == HttpStatusCode.NotModified
            ? ReadQuotesFromCache()
            : await response.Content.ReadAsStringAsync();

        UpdateLocalCache(response.Headers.ETag, quotesJson);
    }
    _quotes = JsonConvert.DeserializeObject<List<QuoteInfo>>(quotesJson);

    PickAndSetQuote();
    IsBusy = false;
}

If the local and remote ETag match, we will not receive any data with the call leaving us with a very small data footprint for this call. The code handling the caching is shown below. Note that for accessing the preferences Xamarin.Essentials were used:

public string CurrentEtagVersion => Preferences.Get(EtagKey, string.Empty);

private void UpdateLocalCache(EntityTagHeaderValue eTag, string quotesJson)
{
    // Only update the cache if we need to
    if (eTag == null || CurrentEtagVersion == eTag.Tag) return;
    Preferences.Set(EtagKey, eTag.Tag);
    File.WriteAllText(_quotesFilename, quotesJson);
}

private string ReadQuotesFromCache()
{
    if (!File.Exists(_quotesFilename)) return string.Empty;
    return File.ReadAllText(_quotesFilename);
}

I will leave it there with this sample but since we are already storing the data in a local cache we could also consider making this app fully Offline capable. With Xamarin Essentials, which we are already using, we can check if we have a network connection and what kind of connection. This information allows us to decide if we want/can access the remote storage or rather load the data from the initial cache.

You can find the entire client sample code on GitHub.

Conclusion

In this post, we saw how you can use Azure Blob storage as a backend service to host the content of your app without having to implement any web server. You can add security layers to the storage. Tracking changes on the backend are provided out of the box via HTTP ETags.

But how much will this cost me? Probably less than you would think but check out the Blob Storage pricing to get your exact number.

0 Comments

When ever you want to display a list or collection of information under iOS tables are often the choice you will end up using. But what if you have content that changes and you want to update the data in a cell? Well that's what bindings are for right? But how do you use bindings in a UITableViewCell? Well let's check it out.

Welcome to our countdown app. As you can see basically it is a list of timers, that are displayed in a cell and updated as they are ticking down.

CellBinding

If we look at the basic setup of a displayed table, we will have UITableView which uses a UITableViewSource for managing the collection to be displayed and the rendering of the UITableViewCells. Then there is the view model which is providing a list of items, in our case timers, that should be displayed. So let's go through each of the items from View Model to Cell.

The View Model

MVVM Light comes with helpers that allow you to convert an ObservableCollection to a UITableViewSource. Choosing this option will further ensure ensure the UI is updated when we add or remove an element to our collection. So let's take it:

public class MainViewModel : ViewModelBase
{
    public MainViewModel()
    {
        // ...
        Countdowns = new ObservableCollection<CountdownViewItem>();
        Countdowns.Add(new CountdownViewItem(new TimeSpan(0, 13, 37)));
    }

    // ...
    public ObservableCollection<CountdownViewItem> Countdowns { get; private set; }

    // ...
}

The CountDownViewItem hold's the state of the individual item. Hence the timer logic is embedded into it and will also implement the RaisePropertyChanged.

public class CountdownViewItem : ViewModelBase
{
    DateTime _expirationTimestamp;

    public CountdownViewItem(TimeSpan timespan)
    {
        if (timespan == null) throw new ArgumentNullException(nameof(timespan));
        _expirationTimestamp = DateTime.UtcNow + timespan;
        Countdown();
    }

    string _remainingTimeString;
    public string RemainingTimeString
    {
        get
        {
            return _remainingTimeString;
        }
        set
        {
            if (value == _remainingTimeString) return;
            _remainingTimeString = value;
            RaisePropertyChanged(nameof(RemainingTimeString));
        }
    }

    private async void Countdown()
    {
        while (DateTime.UtcNow < _expirationTimestamp)
        {
            TimeSpan remainingTime = _expirationTimestamp - DateTime.UtcNow;
            RemainingTimeString = remainingTime.ToString(@"hh\:mm\:ss");
            await Task.Delay(millisecondsDelay: 490);
        }

        RemainingTimeString = "Timer Expired";
    }
}

So let's put these view models to use in the UI.

The Table View

One can choose to create the table either in a story board or in code. Choosing the code path we would have a UIViewController that creates the UITableView, set's the layout and creates the UITableSource from the view models collection:

class ViewController : UIViewController
{
    // private members

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

        // Setup view and layouting

        // Setup bindings
        _tableViewController = Vm.Countdowns.GetController(CreatePersonCell, BindCellDelegate);
        _tableViewController.TableView = CountdownsTableView;

        AddTimerButton.SetCommand("TouchUpInside", Vm.AddCountdownCommand);
    }

    private void BindCellDelegate(UITableViewCell cell, CountdownViewItem countdownViewItem, NSIndexPath path)
    {
        var bindableCell = (CustomCell) cell;
        bindableCell.Configure(countdownViewItem);
    }

    private UITableViewCell CreatePersonCell(NSString cellIdentifier)
    {
        return CountdownsTableView.DequeueReusableCell(nameof(CustomCell));
    }
}

Note we are using a custom cell, and we are registering it to so we can reuse the cell. This will enable our app to reuse created cells I.e. improve the memory footprint, performance and is considered best practice. We will go into the details of that cell shortly.

If you are looking for the storyboard approach, note that this step and only this step will differ. Check out the GitHub repository for a sample.

The Cell

The cell is invoked when via the BindCellDelegate method. So we can populate our cell with binding like this:

public class CustomCell : UITableViewCell
{
    CountdownViewItem _countdownViewItem;
    // ui label and constructors

    private void InitCell()
    {
        // Layout cell
    }

    public Binding<string, string> _timerBinding;

    internal void Configure(CountdownViewItem countdownViewItem)
    {
        _countdownViewItem = countdownViewItem;
        _timerBinding = this.SetBinding(() => _countdownViewItem.RemainingTimeString, () => RemainingTimeLabel.Text);
    }
}

It is very important that you store the passed in view item as member variable in your method. If you do not do this, you will only see the initial value and the binding will never update the view!

Since we are reusing our cells we have to assume for one that our cells might have already been used by a previous item and that there might be an existing binding present.

Let's remember that Bindings boil down to events. And when we register an event handler we should always ensure that we are not creating a memory leak I.e. that we deregister the event handler. MVVM Light provides the method Detach which will deregister the event handler(s) behind the binding. Now why is this important? Well lists tend to come with multiple items. Having a memory leak for each item tends to be a bad design strategy to say the least. Therefor it is important to ensure that the binding in the cells are detached when it is no longer used.

This is also the reason why we are using a custom cell. It allows us to override the method PrepareForReuse which is invoked every time a cell is being reused. In this method we can restore the cell to it's initial state:

public override void PrepareForReuse()
{
    base.PrepareForReuse();
    _timerBinding?.Detach();
}

And that is how you can create bindings in a UITableViewCell.

Conclusion

In this blog post we saw how we can create a binding for a UITableViewCell. We also covered how to avoid memory leaks when reusing cells (and you should generally reuse cells). Generally do not use bindings for static content in cells as they might impact your performance. When ever possible do not change the layout (constraints) due to a change in the content. Though not supported by the UITableViewSource created by MVVM Light at the time of writing, try using different custom cells for different layouts.

You can find the entire app sample on GitHub.

0 Comments

Ever wondered how those slick screen transitions are made by those other apps? Using transitions do give an app that extra polish. But what's even more important it will give your app an edge when it comes to user experience (UX).

In this post we will implement the following transition:

CustomTransition

Before we dive into the code. Be aware that every transition follows the following steps::

  • Configure the transition on the destination View Controller
  • Implement the transition delegate
  • Implement the transition animation
  • If applicable implement the dismiss transition animation

You do not have to implement the dismiss transition. But if you navigate back to the originating page. You really should do this.

Configuring the custom transition

Short digression: There is no consensus in the community what the best approach is to write your UI. Some prefer to do it in code (such as myself) others prefer to use the storyboards. So we will just look at both of them. The good thing is that apart from the configuration the rest of the code is identical.

Configure custom transitions with code

The transition in iOS is actually defined on the target View Controller. In our example we will navigate to the next View Controller as soon as the user selects the button:

_button.TouchUpInside += (e, s) =>
{
    var vc = new ModalViewController(this)
    {
        ModalPresentationStyle = UIModalPresentationStyle.Custom,
        TransitioningDelegate = new GrowTransitioningDelegate(_button)
    };

    NavigationController.PresentViewController(vc, true, null);
};

Note that we configure the ModalPresentationStyle and TransitionDelegate on the view controller that we are navigating to. The GrowTransitionDelegate takes the originating UIView. If you are not using Storyboards you can skip the section and dive right into how the GrowTransitioningDelegate is implemented.

Configure custom transitions with Storyboards

If you are using storyboards you will be familiar with segues. To define a custom transition animation you will have to configure your segue as follows:

Configure the storyboard segue to use Present Modal and leave the Presentation and Transition to Default.

Then in the originating view controller you can override the PrepareForSegue method:

public override void PrepareForSegue(UIStoryboardSegue segue, NSObject sender)
{
    base.PrepareForSegue(segue, sender);

    var destinationVC = segue.DestinationViewController as SecondViewController;
    destinationVC.Callee = this;
    destinationVC.TransitioningDelegate = new GrowTransitioningDelegate(sender as UIView);
    destinationVC.ModalPresentationStyle = UIModalPresentationStyle.Custom;
}

The target view controller in our case is name SecondViewController. It looks fairly similar to the event handler we defined before. On the destination view controller we set the TransitioningDelegate to GrowTransitioningDelegate which takes the originating UIView as constructor parameter. The only difference is that we do no longer pass the instance of the calling view controller as constructor parameter but use a property named Callee on the SecondViewController.

Setting up the delegate

The GrowTransitionDelegate inherits from UIViewControllerTransitioningDelegate from which we can override methods to add a custom animation for presenting as follows:

public class GrowTransitioningDelegate : UIViewControllerTransitioningDelegate
{
    readonly UIView _animationOrigin;

    public GrowTransitioningDelegate(UIView animationOrigin)
    {
        _animationOrigin = animationOrigin;
    }

    public override IUIViewControllerAnimatedTransitioning GetAnimationControllerForPresentedController(UIViewController presented, UIViewController presenting, UIViewController source)
    {
        var customTransition = new GrowTransitionAnimator(_animationOrigin);
        return customTransition;
    }
}

Let's follow along the scenario of the user navigating to another page. The GetAnimationControllerForPresentedController provides an animation implementation that inherits from IUIViewControllerAnimatedTransitioning. The originating UIView, a button in this example, is passed to the animation in the constructor. And that is all the transitioning delegate has to implement. So let's see how we implement the actual animation.

Implementing the transition animation

The animations are defined in a class that inherit from UIViewControllerAnimatedTransitioning. The following two methods have to implemented:

public class GrowTransitionAnimator : UIViewControllerAnimatedTransitioning
{
    readonly UIView _animationOrigin;

    public GrowTransitionAnimator(UIView animationOrigin)
    {
        _animationOrigin = animationOrigin;
    }

    public override async void AnimateTransition(IUIViewControllerContextTransitioning transitionContext)
    {
        // The animation 
    }

    public override double TransitionDuration(IUIViewControllerContextTransitioning transitionContext)
    {
        return 0.3;
    }
}

The TransitionDuration method defines how long the animation will take. Usually this should be around 300 to 500 milliseconds. In the AnimateTransition method the actual transition and animation are defined.

The iOS SDK provides capabilities for creating animations such as transition animations. Using these will significantly reduce the effort required to create an animation to defining the initial and desired target state of a UI object. The rendering of the animation is performed by iOS on the GPU. So not only will your app look great it will also be snappy since GPUs eat transformations for breakfast Smile

The AnimateTransition method usually follows the following pattern:

  1. Get your source and destination view controller and view
  2. Get the animations container view and add the destination
  3. Define the animation states
    1. The final state of the target view
    2. The initial state of the target view
  4. Perform the animation and inform the transition context when it is completed

So let's go through this step by step. First let's get the source and destination view controllers and view:

public override async void AnimateTransition(IUIViewControllerContextTransitioning transitionContext)
{
    // Get the from and to View Controllers and their views
    var fromVC = transitionContext.GetViewControllerForKey(UITransitionContext.FromViewControllerKey);
    var fromView = fromVC.View;
    
    var toVC = transitionContext.GetViewControllerForKey(UITransitionContext.ToViewControllerKey);
    var toView = toVC.View;
     
    // ...
}

Then get the animation container and add the destination to it:

public override async void AnimateTransition(IUIViewControllerContextTransitioning transitionContext)
{
    // ...

    // Add the to view to the transition container view
    var containerView = transitionContext.ContainerView;
    containerView.AddSubview(toView);
        
    // ...
}

Now comes the part where we will define the animation. We want to start our animation in the middle of the button that the user selected I.e. which our animation class has received over the constructor.

public override async void AnimateTransition(IUIViewControllerContextTransitioning transitionContext)
{
    // ...

    // Set the desired target for the transition
    var appearedFrame = transitionContext.GetFinalFrameForViewController(toVC);
    
    // Set how the animation shall start
    var initialFrame = new CGRect(_animationOrigin.Frame.GetMidX(), _animationOrigin.Frame.GetMidY(), 0, 0);
    var finalFrame = appearedFrame;
    toView.Frame = initialFrame;
    
    // ...
}

Now that is left to do is execute the animation and await the result. The result is then used to signal to the transition context that the animation has completed:

public override async void AnimateTransition(IUIViewControllerContextTransitioning transitionContext)
{
    // ...

    var isAnimationCompleted = await UIView.AnimateAsync(TransitionDuration(transitionContext), () => {
        toView.Frame = finalFrame;
    });
    
    transitionContext.CompleteTransition(isAnimationCompleted);
}

And our transition animation is completed. If you are looking to implement a different animation. For instance one where the animation starts from a corner. Changing the origin point of the animation will provide you with the desired effect.

Side effects While animations are great and you will perhaps notice that some UI elements are drawn early and then move into position during the transition. This can feel slightly off. So while working with animations it can make sense to move the layout code of the UI components to the ViewDidAppear method.

Creating the dismiss animation

For having the inverse animation for dismissing the view. First the GetAnimationControllerForDismissedController method has to be overwritten in the delegate class (same one as before):

public class GrowTransitioningDelegate : UIViewControllerTransitioningDelegate
{
    readonly UIView _animationOrigin;

    public GrowTransitioningDelegate(UIView animationOrigin)
    {
        _animationOrigin = animationOrigin;
    }

    public override IUIViewControllerAnimatedTransitioning GetAnimationControllerForPresentedController(UIViewController presented, UIViewController presenting, UIViewController source)
    {
        // ...
    }

    public override IUIViewControllerAnimatedTransitioning GetAnimationControllerForDismissedController(UIViewController dismissed)
    {
        var customTransition = new ShrinkTransitionAnimator(_animationOrigin);
        return customTransition;
    }
}

Then create an implementation which inherits UIViewControllerAnimatedTransitioning and has the desired reverse animation.

public class ShrinkTransitionAnimator : UIViewControllerAnimatedTransitioning
{
    readonly UIView _animationOrigin;

    public ShrinkTransitionAnimator(UIView animationTarget)
    {
        _animationOrigin = animationTarget;
    }

    public override async void AnimateTransition(IUIViewControllerContextTransitioning transitionContext)
    {
        // Get the from and to View Controllers and their views
        var fromVC = transitionContext.GetViewControllerForKey(UITransitionContext.FromViewControllerKey);
        var fromView = fromVC.View;

        var toVC = transitionContext.GetViewControllerForKey(UITransitionContext.ToViewControllerKey);
        var toView = toVC.View;

        // Add the to view to the transition container view
        var containerView = transitionContext.ContainerView;

        // Set the desired target for the transition
        var appearedFrame = transitionContext.GetFinalFrameForViewController(fromVC);

        // Set how the animation shall end
        var finalFrame = new CGRect(_animationOrigin.Frame.GetMidX(), _animationOrigin.Frame.GetMidY(), 0, 0);
        fromView.Frame = appearedFrame;

        var isAnimationCompleted = await UIView.AnimateAsync(TransitionDuration(transitionContext), () => {
            fromView.Frame = finalFrame;
        });

        fromView.RemoveFromSuperview();

        transitionContext.CompleteTransition(isAnimationCompleted);
    }

    public override double TransitionDuration(IUIViewControllerContextTransitioning transitionContext)
    {
        return 0.3;
    }
}

Though looking similar there are a few key points. For one the origin this time is the view controller that we want to dismiss. Since it is already part of the View tree it no longer has to be added. Quite the contrary you will actually want to remove it once the animation is completed.

Conclusion

In this post we went through the steps required to create a custom transition. Further we looked at how we can implement the dismiss animation. The steps can be reused for different kind of animations which are defined in the implementation of the UIViewControllerAnimatedTransitioning class.

You can find the complete code sample on GitHub.