Click or drag to resize

OfflineMapParametersKey Class

A key comprising of a service URL and type of an online service. The key is used to lookup values in a dictionary exposed by a GenerateOfflineMapParameterOverrides.
Inheritance Hierarchy
SystemObject
  Esri.ArcGISRuntime.Tasks.OfflineOfflineMapParametersKey

Namespace:  Esri.ArcGISRuntime.Tasks.Offline
Assembly:  Esri.ArcGISRuntime (in Esri.ArcGISRuntime.dll) Version: 100.11.0
Syntax
public sealed class OfflineMapParametersKey

The OfflineMapParametersKey type exposes the following members.

Constructors
  NameDescription
Public methodCode exampleOfflineMapParametersKey(Layer)
Initializes a new instance of the OfflineMapParametersKey class for the given layer.
Public methodOfflineMapParametersKey(ServiceFeatureTable)
Initializes a new instance of the OfflineMapParametersKey class for the given table.
Public methodOfflineMapParametersKey(UtilityNetwork)
Initializes a new instance of the OfflineMapParametersKey class for the specified UtilityNetwork.
Top
Properties
  NameDescription
Public propertyServiceUrl
Gets the service URL of this key.
Public propertyType
Gets the parameter type of this key.
Top
Methods
Remarks
GenerateOfflineMapParameterOverrides consist of instances of data type specific parameters GenerateGeodatabaseParameters, ExportTileCacheParameters and ExportVectorTilesParameters. Each instance is associated with the service URL that the parameters will be sent to when taking the given data offline. The data type specific parameters can be retrieved from their respective dictionaries on GenerateOfflineMapParameterOverrides with an instance of OfflineMapParametersKey. A key instance for a particular layer can be constructed by calling OfflineMapParametersKey(Layer). For a table, call OfflineMapParametersKey(ServiceFeatureTable). For a utility network, call OfflineMapParametersKey(UtilityNetwork). Use the key with the dictionary returned by one of these properties: GenerateGeodatabaseParameters, ExportVectorTilesParameters, ExportTileCacheParameters. The particular dictionary property will be determined by the Type property. Note that a given parameters key/value pair may be shared by multiple layers in the map: for example a single GenerateGeodatabaseParameters could represent the online settings for multiple feature layers and tables which all access the same underlying feature service.
Examples

UWP

Example Name: GenerateOfflineMapWithOverrides

Take a web map offline with additional options for each layer.

Code example screen shot.

C#
// Copyright 2019 Esri.
// 
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
// You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific 
// language governing permissions and limitations under the License.

using Esri.ArcGISRuntime.Data;
using Esri.ArcGISRuntime.Geometry;
using Esri.ArcGISRuntime.Mapping;
using Esri.ArcGISRuntime.Symbology;
using Esri.ArcGISRuntime.Tasks;
using Esri.ArcGISRuntime.Tasks.Offline;
using Esri.ArcGISRuntime.UI;
using Esri.ArcGISRuntime.ArcGISServices;
using Esri.ArcGISRuntime.UI.Controls;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Windows.UI.Popups;
using Windows.UI.Xaml.Controls;
using Esri.ArcGISRuntime.Portal;
using Esri.ArcGISRuntime.Security;

namespace ArcGISRuntime.UWP.Samples.GenerateOfflineMapWithOverrides
{
    [ArcGISRuntime.Samples.Shared.Attributes.Sample(
        name: "Generate offline map (overrides)",
        category: "Map",
        description: "Take a web map offline with additional options for each layer.",
        instructions: "Modify the overrides parameters:",
        tags: new[] { "LOD", "adjust", "download", "extent", "filter", "offline", "override", "parameters", "reduce", "scale range", "setting" })]
    public partial class GenerateOfflineMapWithOverrides
    {
        // The job to generate an offline map.
        private GenerateOfflineMapJob _generateOfflineMapJob;

        // The extent of the data to take offline.
        private Envelope _areaOfInterest = new Envelope(-88.1541, 41.7690, -88.1471, 41.7720, SpatialReferences.Wgs84);

        // The ID for a web map item hosted on the server (water network map of Naperville IL).
        private const string WebMapId = "acc027394bc84c2fb04d1ed317aac674";

        public GenerateOfflineMapWithOverrides()
        {
            InitializeComponent();

            // Load the web map, show area of interest, restrict map interaction, and set up authorization. 
            Initialize();
        }

        private async void Initialize()
        {
            try
            {
                // Call a function to set up the AuthenticationManager for OAuth.
                SetOAuthInfo();

                // Create the ArcGIS Online portal.
                ArcGISPortal portal = await ArcGISPortal.CreateAsync();

                // Get the Naperville water web map item using its ID.
                PortalItem webmapItem = await PortalItem.CreateAsync(portal, WebMapId);

                // Create a map from the web map item.
                Map onlineMap = new Map(webmapItem);

                // Display the map in the MapView.
                MyMapView.Map = onlineMap;

                // Disable user interactions on the map (no panning or zooming from the initial extent).
                MyMapView.InteractionOptions = new MapViewInteractionOptions
                {
                    IsEnabled = false
                };

                // Create a graphics overlay for the extent graphic and apply a renderer.
                SimpleLineSymbol aoiOutlineSymbol = new SimpleLineSymbol(SimpleLineSymbolStyle.Solid, System.Drawing.Color.Red, 3);
                GraphicsOverlay extentOverlay = new GraphicsOverlay
                {
                    Renderer = new SimpleRenderer(aoiOutlineSymbol)
                };
                MyMapView.GraphicsOverlays.Add(extentOverlay);

                // Add a graphic to show the area of interest (extent) that will be taken offline.
                Graphic aoiGraphic = new Graphic(_areaOfInterest);
                extentOverlay.Graphics.Add(aoiGraphic);

                // Hide the map loading progress indicator.
                LoadingIndicator.Visibility = Windows.UI.Xaml.Visibility.Collapsed;

                // Clean up any existing output data folders that might exist from running this sample previously. 
                // The output data folder is where the results of the taking the web map offline get stored on the device.
                MyMapView.Unloaded += (s, e) =>
                {
                    // Find output mobile map folders in the temp directory.
                    string[] outputFolders = Directory.GetDirectories(Environment.ExpandEnvironmentVariables("%TEMP%"), "NapervilleWaterNetwork*");

                    // Loop through the folder names and delete them.
                    foreach (string dir in outputFolders)
                    {
                        try
                        {
                            // Delete the folder.
                            Directory.Delete(dir, true);
                        }
                        catch (Exception)
                        {
                            // Ignore exceptions (files might be locked, for example).
                        }
                    }
                };
            }
            catch (Exception ex)
            {
                MessageDialog dialog = new MessageDialog(ex.ToString(), "Error loading map");
                await dialog.ShowAsync();
            }
        }

        private async void TakeMapOfflineButton_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
        {
            // Create a new folder for the output mobile map.
            string packagePath = Path.Combine(Environment.ExpandEnvironmentVariables("%TEMP%"), @"NapervilleWaterNetwork");
            int num = 1;
            while (Directory.Exists(packagePath))
            {
                packagePath = Path.Combine(Environment.ExpandEnvironmentVariables("%TEMP%"), @"NapervilleWaterNetwork" + num.ToString());
                num++;
            }

            // Create the output directory.
            Directory.CreateDirectory(packagePath);

            try
            {
                // Show the progress indicator while the job is running.
                BusyIndicator.Visibility = Windows.UI.Xaml.Visibility.Visible;

                // Create an offline map task with the current (online) map.
                OfflineMapTask takeMapOfflineTask = await OfflineMapTask.CreateAsync(MyMapView.Map);

                // Create the default parameters for the task, pass in the area of interest.
                GenerateOfflineMapParameters parameters = await takeMapOfflineTask.CreateDefaultGenerateOfflineMapParametersAsync(_areaOfInterest);

                #region overrides

                // Generate parameter overrides for more in-depth control of the job.
                GenerateOfflineMapParameterOverrides overrides = await takeMapOfflineTask.CreateGenerateOfflineMapParameterOverridesAsync(parameters);

                // Configure the overrides using helper methods.
                ConfigureTileLayerOverrides(overrides);
                ConfigureLayerExclusion(overrides);
                CropWaterPipes(overrides);
                ApplyFeatureFilter(overrides);

                // Create the job with the parameters and output location.
                _generateOfflineMapJob = takeMapOfflineTask.GenerateOfflineMap(parameters, packagePath, overrides);

                #endregion overrides

                // Handle the progress changed event for the job.
                _generateOfflineMapJob.ProgressChanged += OfflineMapJob_ProgressChanged;

                // Await the job to generate geodatabases, export tile packages, and create the mobile map package.
                GenerateOfflineMapResult results = await _generateOfflineMapJob.GetResultAsync();

                // Check for job failure (writing the output was denied, e.g.).
                if (_generateOfflineMapJob.Status != JobStatus.Succeeded)
                {
                    MessageDialog dialog = new MessageDialog("Generate offline map package failed.", "Job status");
                    await dialog.ShowAsync();
                    BusyIndicator.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
                }

                // Check for errors with individual layers.
                if (results.LayerErrors.Any())
                {
                    // Build a string to show all layer errors.
                    System.Text.StringBuilder errorBuilder = new System.Text.StringBuilder();
                    foreach (KeyValuePair<Layer, Exception> layerError in results.LayerErrors)
                    {
                        errorBuilder.AppendLine(string.Format("{0} : {1}", layerError.Key.Id, layerError.Value.Message));
                    }

                    // Show layer errors.
                    string errorText = errorBuilder.ToString();
                    MessageDialog dialog = new MessageDialog(errorText, "Layer errors");
                    await dialog.ShowAsync();
                }

                // Display the offline map.
                MyMapView.Map = results.OfflineMap;

                // Apply the original viewpoint for the offline map.
                MyMapView.SetViewpoint(new Viewpoint(_areaOfInterest));

                // Enable map interaction so the user can explore the offline data.
                MyMapView.InteractionOptions.IsEnabled = true;

                // Hide the "Take map offline" button.
                TakeOfflineArea.Visibility = Windows.UI.Xaml.Visibility.Collapsed;

                // Show a message that the map is offline.
                MessageArea.Visibility = Windows.UI.Xaml.Visibility.Visible;
            }
            catch (TaskCanceledException)
            {
                // Generate offline map task was canceled.
                MessageDialog dialog = new MessageDialog("Taking map offline was canceled");
                await dialog.ShowAsync();
            }
            catch (Exception ex)
            {
                // Exception while taking the map offline.
                MessageDialog dialog = new MessageDialog(ex.Message, "Offline map error");
                await dialog.ShowAsync();
            }
            finally
            {
                // Hide the activity indicator when the job is done.
                BusyIndicator.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
            }
        }

        #region overrides

        private void ConfigureTileLayerOverrides(GenerateOfflineMapParameterOverrides overrides)
        {
            // Create a parameter key for the first basemap layer.
            OfflineMapParametersKey basemapKey = new OfflineMapParametersKey(MyMapView.Map.Basemap.BaseLayers.First());

            // Get the export tile cache parameters for the layer key.
            ExportTileCacheParameters basemapParams = overrides.ExportTileCacheParameters[basemapKey];

            // Clear the existing level IDs.
            basemapParams.LevelIds.Clear();

            // Get the min and max scale from the UI.
            int minScale = (int)MinScaleEntry.Value;
            int maxScale = (int)MaxScaleEntry.Value;

            // Re-add selected scales.
            for (int i = minScale; i < maxScale; i++)
            {
                basemapParams.LevelIds.Add(i);
            }

            // Expand the area of interest based on the specified buffer distance.
            int bufferDistance = (int)ExtentBufferEntry.Value;
            basemapParams.AreaOfInterest = GeometryEngine.BufferGeodetic(_areaOfInterest, bufferDistance, LinearUnits.Meters);
        }

        private void ConfigureLayerExclusion(GenerateOfflineMapParameterOverrides overrides)
        {
            // Apply layer exclusions as specified in the UI.
            if (ServiceConnCheckbox.IsChecked == false)
            {
                ExcludeLayerByName("Service Connection", overrides);
            }

            if (SysValvesLayerCheckbox.IsChecked == false)
            {
                ExcludeLayerByName("System Valve", overrides);
            }
        }

        private void CropWaterPipes(GenerateOfflineMapParameterOverrides overrides)
        {
            if (CropLayerCheckbox.IsChecked == true)
            {
                // Get the ID of the water pipes layer.
                long targetLayerId = GetServiceLayerId(GetLayerByName("Main"));

                // For each layer option.
                foreach (GenerateLayerOption layerOption in GetAllLayerOptions(overrides))
                {
                    // If the option's LayerId matches the selected layer's ID.
                    if (layerOption.LayerId == targetLayerId)
                    {
                        layerOption.UseGeometry = true;
                    }
                }
            }
        }

        private void ApplyFeatureFilter(GenerateOfflineMapParameterOverrides overrides)
        {
            // For each layer option.
            foreach (GenerateLayerOption option in GetAllLayerOptions(overrides))
            {
                // If the option's LayerId matches the selected layer's ID.
                if (option.LayerId == GetServiceLayerId(GetLayerByName("Hydrant")))
                {
                    // Apply the where clause.
                    option.WhereClause = "FLOW >= " + (int)FlowRateFilterEntry.Value;

                    // Configure the option to use the where clause.
                    option.QueryOption = GenerateLayerQueryOption.UseFilter;
                }
            }
        }

        private IList<GenerateLayerOption> GetAllLayerOptions(GenerateOfflineMapParameterOverrides overrides)
        {
            // Find the first feature layer.
            FeatureLayer targetLayer = MyMapView.Map.OperationalLayers.OfType<FeatureLayer>().First();

            // Get the key for the layer.
            OfflineMapParametersKey layerKey = new OfflineMapParametersKey(targetLayer);

            // Use that key to get the generate options for the layer.
            GenerateGeodatabaseParameters generateParams = overrides.GenerateGeodatabaseParameters[layerKey];

            // Return the layer options.
            return generateParams.LayerOptions;
        }

        private void ExcludeLayerByName(string layerName, GenerateOfflineMapParameterOverrides overrides)
        {
            // Get the feature layer with the specified name.
            FeatureLayer targetLayer = GetLayerByName(layerName);

            // Get the layer's ID.
            long targetLayerId = GetServiceLayerId(targetLayer);

            // Create a layer key for the selected layer.
            OfflineMapParametersKey layerKey = new OfflineMapParametersKey(targetLayer);

            // Get the parameters for the layer.
            GenerateGeodatabaseParameters generateParams = overrides.GenerateGeodatabaseParameters[layerKey];

            // Get the layer options for the layer.
            IList<GenerateLayerOption> layerOptions = generateParams.LayerOptions;

            // Find the layer option matching the ID.
            GenerateLayerOption targetLayerOption = layerOptions.First(layer => layer.LayerId == targetLayerId);

            // Remove the layer option.
            layerOptions.Remove(targetLayerOption);
        }

        private FeatureLayer GetLayerByName(string layerName)
        {
            // Get the first map in the operational layers collection that is a feature layer with name matching layerName
            return MyMapView.Map.OperationalLayers.OfType<FeatureLayer>().First(layer => layer.Name == layerName);
        }

        private long GetServiceLayerId(FeatureLayer layer)
        {
            // Find the service feature table for the layer; this assumes the layer is backed by a service feature table.
            ServiceFeatureTable serviceTable = (ServiceFeatureTable) layer.FeatureTable;

            // Return the layer ID.
            return serviceTable.LayerInfo.ServiceLayerId;
        }

        #endregion overrides

        // Show changes in job progress.
        private async void OfflineMapJob_ProgressChanged(object sender, EventArgs e)
        {
            // Get the job.
            GenerateOfflineMapJob job = sender as GenerateOfflineMapJob;

            // Dispatch to the UI thread.
            await Dispatcher.RunAsync( Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
            {
                // Show the percent complete and update the progress bar.
                Percentage.Text = job.Progress > 0 ? job.Progress.ToString() + " %" : string.Empty;
                ProgressBar.Value = job.Progress;
            });
        }

        private void CancelJobButton_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
        {
            // The user canceled the job.
            _generateOfflineMapJob.Cancel();
        }

        #region Authentication
        // Constants for OAuth-related values.
        // - The URL of the portal to authenticate with (ArcGIS Online).
        private const string ServerUrl = "https://www.arcgis.com/sharing/rest";
        // - The Client ID for an app registered with the server (the ID below is for a public app created by the ArcGIS Runtime team).
        private const string AppClientId = @"lgAdHkYZYlwwfAhC";
        // - An optional client secret for the app (only needed for the OAuthAuthorizationCode authorization type).
        private const string ClientSecret = "";
        // - A URL for redirecting after a successful authorization (this must be a URL configured with the app).
        private const string OAuthRedirectUrl = @"my-ags-app://auth";

        private void SetOAuthInfo()
        {
            // Register the server information with the AuthenticationManager.
            ServerInfo serverInfo = new ServerInfo
            {
                ServerUri = new Uri(ServerUrl),
                TokenAuthenticationType = TokenAuthenticationType.OAuthImplicit,
                OAuthClientInfo = new OAuthClientInfo
                {
                    ClientId = AppClientId,
                    RedirectUri = new Uri(OAuthRedirectUrl)
                }
            };

            // Register this server with AuthenticationManager.
            AuthenticationManager.Current.RegisterServer(serverInfo);

            // Use a function in this class to challenge for credentials.
            AuthenticationManager.Current.ChallengeHandler = new ChallengeHandler(CreateCredentialAsync);

            // Note: In a WPF app, you need to associate a custom IOAuthAuthorizeHandler component with the AuthenticationManager to 
            //     handle showing OAuth login controls (AuthenticationManager.Current.OAuthAuthorizeHandler = new MyOAuthAuthorize();).
            //     The UWP AuthenticationManager, however, uses a built-in IOAuthAuthorizeHandler (based on WebAuthenticationBroker).
        }

        public async Task<Credential> CreateCredentialAsync(CredentialRequestInfo info)
        {
            // ChallengeHandler function for AuthenticationManager that will be called whenever a secured resource is accessed.
            Credential credential = null;

            try
            {
                // AuthenticationManager will handle challenging the user for credentials.
                credential = await AuthenticationManager.Current.GenerateCredentialAsync(info.ServiceUri);
            }
            catch (Exception)
            {
                // Exception will be reported in calling function.
                throw;
            }

            return credential;
        }
        #endregion
    }
}
XAML
<UserControl
    x:Class="ArcGISRuntime.UWP.Samples.GenerateOfflineMapWithOverrides.GenerateOfflineMapWithOverrides"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:esriUI="using:Esri.ArcGISRuntime.UI.Controls">
    <Grid>
        <esriUI:MapView x:Name="MyMapView" />

        <Border x:Name="TakeOfflineArea" Style="{StaticResource BorderStyle}">
            <Grid ColumnSpacing="5" RowSpacing="5">
                <Grid.Resources>
                    <Style TargetType="TextBlock">
                        <Setter Property="Margin" Value="0,0,0,5" />
                    </Style>
                    <Style TargetType="TextBox">
                        <Setter Property="Margin" Value="0,0,0,5" />
                        <Setter Property="VerticalContentAlignment" Value="Center"></Setter>
                    </Style>
                    <Style TargetType="CheckBox">
                        <Setter Property="Margin" Value="0,0,0,5" />
                    </Style>
                    <Style TargetType="Slider">
                        <Setter Property="Margin" Value="0,-8,0,0" />
                        <Setter Property="VerticalAlignment" Value="Center" />
                    </Style>
                </Grid.Resources>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition Width="*" />
                    <ColumnDefinition Width="Auto" />
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                </Grid.RowDefinitions>
                <TextBlock Text="Adjust the basemap"
                           FontWeight="SemiBold"
                           Grid.Row="0" Grid.Column="0" />
                <TextBlock Text="Min scale:"
                           Grid.Row="1" Grid.Column="0" />
                <Slider x:Name="MinScaleEntry"
                         Value="0" SnapsTo="StepValues" StepFrequency="1"
                         Minimum="0" Maximum="23"
                         Grid.Row="1" Grid.Column="1" />
                <TextBlock Text="{Binding ElementName=MinScaleEntry,Path=Value}"
                           Grid.Row="1" Grid.Column="2" />
                <TextBlock Text="Max scale:"
                           Grid.Row="2" Grid.Column="0" />
                <Slider x:Name="MaxScaleEntry"
                         Value="23" SnapsTo="StepValues" StepFrequency="1"
                         Minimum="0" Maximum="23"
                         Grid.Row="2" Grid.Column="1" />
                <TextBlock Text="{Binding ElementName=MaxScaleEntry,Path=Value}"
                           Grid.Row="2" Grid.Column="2" />
                <TextBlock Text="Extent buffer distance (m):"
                           Grid.Row="3" Grid.Column="0" />
                <Slider x:Name="ExtentBufferEntry"
                         Value="250" SnapsTo="StepValues" StepFrequency="1"
                         Minimum="0" Maximum="500"
                         Grid.Row="3" Grid.Column="1" />
                <TextBlock Text="{Binding ElementName=ExtentBufferEntry,Path=Value}"
                           Grid.Row="3" Grid.Column="2" />
                <TextBlock Text="Choose layers"
                           FontWeight="SemiBold"
                           Grid.Row="4" Grid.Column="0" />
                <CheckBox x:Name="SysValvesLayerCheckbox"
                          Content="System Valves"
                          IsChecked="True"
                          Grid.Row="5" Grid.Column="0" />
                <CheckBox x:Name="ServiceConnCheckbox"
                          Content="Service Connections"
                          Grid.Row="6" Grid.Column="0" />
                <TextBlock Text="Apply a feature layer filer"
                           FontWeight="SemiBold"
                           Grid.Row="7" Grid.Column="0" />
                <TextBlock Text="Min Hydrant Flow Rate (GPM):"
                           VerticalAlignment="Center"
                           Grid.Row="8" Grid.Column="0" />
                <Slider x:Name="FlowRateFilterEntry"
                         Value="500" SnapsTo="StepValues" StepFrequency="1"
                         Minimum="0" Maximum="999"
                         Margin="0,8,0,0"
                         Grid.Row="8" Grid.Column="1" />
                <TextBlock Text="{Binding ElementName=FlowRateFilterEntry,Path=Value}"
                           Grid.Row="8" Grid.Column="2"
                           VerticalAlignment="Center"
                           MinWidth="25" />
                <TextBlock Text="Crop layer to extent"
                           FontWeight="SemiBold"
                           Grid.Row="9" Grid.Column="0" />
                <CheckBox x:Name="CropLayerCheckbox"
                          Content="Water pipes"
                          IsChecked="True"
                          Grid.Row="10" Grid.Column="0" />
                <Button Content="Take map offline"
                        Grid.Row="11" Grid.Column="0" Grid.ColumnSpan="3"
                        HorizontalAlignment="Stretch"
                        IsEnabled="True" 
                        Click="TakeMapOfflineButton_Click" />
            </Grid>
        </Border>

        <Border x:Name="MessageArea" Visibility="Collapsed"
                Background="White" BorderBrush="Black" BorderThickness="1"
                HorizontalAlignment="Right" VerticalAlignment="Top"
                Margin="30" Padding="5" Width="450">
            <StackPanel>
                <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Margin="10"
                           FontWeight="Bold"
                           Text="Map is offline!" />
            </StackPanel>
        </Border>

        <Grid x:Name="BusyIndicator" Background="#807f7f7f" Visibility="Collapsed">
            <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
                <Grid.RowDefinitions>
                    <RowDefinition Height="auto" />
                    <RowDefinition Height="auto" />
                    <RowDefinition Height="auto" />
                </Grid.RowDefinitions>
                <TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Margin="10"
                           Foreground="White" FontSize="18">
                    <Run Text="Generating offline map... " />
                    <Run x:Name="Percentage" Text="" />
                </TextBlock>
                <ProgressBar x:Name="ProgressBar"
                             Grid.Row="1"
                             Minimum="0" Maximum="100"
                             IsEnabled="True"
                             HorizontalAlignment="Center" VerticalAlignment="Center"
                             Width="100" Height="10" Margin="0,0,0,10" />
                <Button Grid.Row="2"
                        Content="Cancel"
                        Click="CancelJobButton_Click"
                        HorizontalAlignment="Center"
                        Width="100" />
            </Grid>
        </Grid>
        <Grid x:Name="LoadingIndicator"
              Background="#807f7f7f"
              Visibility="Visible">
            <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
                <Grid.RowDefinitions>
                    <RowDefinition Height="auto" />
                    <RowDefinition Height="auto" />
                </Grid.RowDefinitions>
                <TextBlock Text="Loading online map..."
                           Foreground="White" FontSize="18"
                           Margin="10" />
                <ProgressBar Grid.Row="1"
                             IsEnabled="True" IsIndeterminate="True"
                             Width="100" Height="10"
                             HorizontalAlignment="Center" VerticalAlignment="Center" />
            </Grid>
        </Grid>
    </Grid>
</UserControl>
Examples

Xamarin Forms UWP

Example Name: GenerateOfflineMapWithOverrides

Take a web map offline with additional options for each layer.

Code example screen shot.

C#
using System;
using System.Collections.Generic;
using System.Linq;
using Esri.ArcGISRuntime.Data;
using Esri.ArcGISRuntime.Geometry;
using Esri.ArcGISRuntime.Mapping;
using Esri.ArcGISRuntime.Tasks.Offline;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace ArcGISRuntimeXamarin.Samples.GenerateOfflineMapWithOverrides
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class ConfigureOverridesPage : ContentPage
    {
        private Esri.ArcGISRuntime.Mapping.Map _map;
        private Envelope _areaOfInterest = new Envelope(-88.1541, 41.7690, -88.1471, 41.7720, SpatialReferences.Wgs84);
        private GenerateOfflineMapParameterOverrides _overrides;

        public ConfigureOverridesPage ()
        {
            InitializeComponent ();
        }

        public ConfigureOverridesPage(GenerateOfflineMapParameterOverrides overrides, Esri.ArcGISRuntime.Mapping.Map map)
        {
            InitializeComponent();

            // Hold a reference to the map & overrides.
            _map = map;
            _overrides = overrides;
        }

        #region overrides

        private void ConfigureTileLayerOverrides(GenerateOfflineMapParameterOverrides overrides)
        {
            // Create a parameter key for the first basemap layer.
            OfflineMapParametersKey basemapKey = new OfflineMapParametersKey(_map.Basemap.BaseLayers.First());

            // Get the export tile cache parameters for the layer key.
            ExportTileCacheParameters basemapParams = overrides.ExportTileCacheParameters[basemapKey];

            // Clear the existing level IDs.
            basemapParams.LevelIds.Clear();

            // Get the min and max scale from the UI.
            int minScale = (int)MinScaleEntry.Value;
            int maxScale = (int) MaxScaleEntry.Value;

            // Re-add selected scales.
            for (int i = minScale; i < maxScale; i++)
            {
                basemapParams.LevelIds.Add(i);
            }

            // Expand the area of interest based on the specified buffer distance.
            int bufferDistance = (int)ExtentBufferEntry.Value;
            basemapParams.AreaOfInterest = GeometryEngine.BufferGeodetic(_areaOfInterest, bufferDistance, LinearUnits.Meters);
        }

        private void ConfigureLayerExclusion(GenerateOfflineMapParameterOverrides overrides)
        {
            // Apply layer exclusions as specified in the UI.
            if (ServiceConnCheckbox.IsToggled == false)
            {
                ExcludeLayerByName("Service Connection", overrides);
            }

            if (SysValvesLayerCheckbox.IsToggled == false)
            {
                ExcludeLayerByName("System Valve", overrides);
            }
        }

        private void CropWaterPipes(GenerateOfflineMapParameterOverrides overrides)
        {
            if (CropLayerCheckbox.IsToggled)
            {
                // Get the ID of the water pipes layer.
                long targetLayerId = GetServiceLayerId(GetLayerByName("Main"));

                // For each layer option.
                foreach (GenerateLayerOption layerOption in GetAllLayerOptions(overrides))
                {
                    // If the option's LayerId matches the selected layer's ID.
                    if (layerOption.LayerId == targetLayerId)
                    {
                        layerOption.UseGeometry = true;
                    }
                }
            }
        }

        private void ApplyFeatureFilter(GenerateOfflineMapParameterOverrides overrides)
        {
            // For each layer option.
            foreach (GenerateLayerOption option in GetAllLayerOptions(overrides))
            {
                // If the option's LayerId matches the selected layer's ID.
                if (option.LayerId == GetServiceLayerId(GetLayerByName("Hydrant")))
                {
                    // Apply the where clause.
                    option.WhereClause = "FLOW >= " + (int)FlowRateFilterEntry.Value;
                    // Configure the option to use the where clause.
                    option.QueryOption = GenerateLayerQueryOption.UseFilter;
                }
            }
        }

        private IList<GenerateLayerOption> GetAllLayerOptions(GenerateOfflineMapParameterOverrides overrides)
        {
            // Find the first feature layer.
            FeatureLayer targetLayer = _map.OperationalLayers.OfType<FeatureLayer>().First();

            // Get the key for the layer.
            OfflineMapParametersKey layerKey = new OfflineMapParametersKey(targetLayer);

            // Use that key to get the generate options for the layer.
            GenerateGeodatabaseParameters generateParams = overrides.GenerateGeodatabaseParameters[layerKey];

            // Return the layer options.
            return generateParams.LayerOptions;
        }

        private void ExcludeLayerByName(string layerName, GenerateOfflineMapParameterOverrides overrides)
        {
            // Get the feature layer with the specified name.
            FeatureLayer targetLayer = GetLayerByName(layerName);

            // Get the layer's ID.
            long targetLayerId = GetServiceLayerId(targetLayer);

            // Create a layer key for the selected layer.
            OfflineMapParametersKey layerKey = new OfflineMapParametersKey(targetLayer);

            // Get the parameters for the layer.
            GenerateGeodatabaseParameters generateParams = overrides.GenerateGeodatabaseParameters[layerKey];

            // Get the layer options for the layer.
            IList<GenerateLayerOption> layerOptions = generateParams.LayerOptions;

            // Find the layer option matching the ID.
            GenerateLayerOption targetLayerOption = layerOptions.First(layer => layer.LayerId == targetLayerId);

            // Remove the layer option.
            layerOptions.Remove(targetLayerOption);
        }

        private FeatureLayer GetLayerByName(string layerName)
        {
            // Get the first map in the operational layers collection that is a feature layer with name matching layerName
            return _map.OperationalLayers.OfType<FeatureLayer>().First(layer => layer.Name == layerName);
        }

        private long GetServiceLayerId(FeatureLayer layer)
        {
            // Find the service feature table for the layer; this assumes the layer is backed by a service feature table.
            ServiceFeatureTable serviceTable = (ServiceFeatureTable) layer.FeatureTable;

            // Return the layer ID.
            return serviceTable.LayerInfo.ServiceLayerId;
        }

        #endregion overrides

        private void TakeMapOffline_Clicked(object sender, EventArgs e)
        {
            // Configure the overrides using helper methods.
            ConfigureTileLayerOverrides(_overrides);
            ConfigureLayerExclusion(_overrides);
            CropWaterPipes(_overrides);
            ApplyFeatureFilter(_overrides);

            // The main sample page continues when OnDisappearing is called.
            // OnDisappearing is called by Xamarin.Forms when navigation away from this page happens.
            Navigation.PopModalAsync(true);
        }
    }
}
C#
// Copyright 2019 Esri.
// 
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
// You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific 
// language governing permissions and limitations under the License.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Esri.ArcGISRuntime.Geometry;
using Esri.ArcGISRuntime.Mapping;
using Esri.ArcGISRuntime.Symbology;
using Esri.ArcGISRuntime.Tasks;
using Esri.ArcGISRuntime.Tasks.Offline;
using Esri.ArcGISRuntime.UI;
using Esri.ArcGISRuntime.Portal;
using Esri.ArcGISRuntime.Security;
using Xamarin.Forms;
#if __IOS__
using Xamarin.Auth;
using UIKit;
#endif

#if __ANDROID__
using Android.App;
using Application = Xamarin.Forms.Application;
using Xamarin.Auth;
#endif

namespace ArcGISRuntimeXamarin.Samples.GenerateOfflineMapWithOverrides
{
    [ArcGISRuntime.Samples.Shared.Attributes.Sample(
        name: "Generate offline map (overrides)",
        category: "Map",
        description: "Take a web map offline with additional options for each layer.",
        instructions: "Modify the overrides parameters:",
        tags: new[] { "LOD", "adjust", "download", "extent", "filter", "offline", "override", "parameters", "reduce", "scale range", "setting" })]
    public partial class GenerateOfflineMapWithOverrides : ContentPage, IOAuthAuthorizeHandler
    {
        // The job to generate an offline map.
        private GenerateOfflineMapJob _generateOfflineMapJob;

        // The extent of the data to take offline.
        private Envelope _areaOfInterest = new Envelope(-88.1541, 41.7690, -88.1471, 41.7720, SpatialReferences.Wgs84);

        // The ID for a web map item hosted on the server (water network map of Naperville IL).
        private const string WebMapId = "acc027394bc84c2fb04d1ed317aac674";

        public GenerateOfflineMapWithOverrides()
        {
            InitializeComponent();

            // Load the web map, show area of interest, restrict map interaction, and set up authorization. 
            Initialize();
        }

        private async void Initialize()
        {
            try
            {
                // Call a function to set up the AuthenticationManager for OAuth.
                SetOAuthInfo();

                // Create the ArcGIS Online portal.
                ArcGISPortal portal = await ArcGISPortal.CreateAsync();

                // Get the Naperville water web map item using its ID.
                PortalItem webmapItem = await PortalItem.CreateAsync(portal, WebMapId);

                // Create a map from the web map item.
                Map onlineMap = new Map(webmapItem);

                // Display the map in the MapView.
                MyMapView.Map = onlineMap;

                // Disable user interactions on the map (no panning or zooming from the initial extent).
                MyMapView.InteractionOptions = new MapViewInteractionOptions
                {
                    IsEnabled = false
                };

                // Create a graphics overlay for the extent graphic and apply a renderer.
                SimpleLineSymbol aoiOutlineSymbol = new SimpleLineSymbol(SimpleLineSymbolStyle.Solid, System.Drawing.Color.Red, 3);
                GraphicsOverlay extentOverlay = new GraphicsOverlay
                {
                    Renderer = new SimpleRenderer(aoiOutlineSymbol)
                };
                MyMapView.GraphicsOverlays.Add(extentOverlay);

                // Add a graphic to show the area of interest (extent) that will be taken offline.
                Graphic aoiGraphic = new Graphic(_areaOfInterest);
                extentOverlay.Graphics.Add(aoiGraphic);

                // Hide the map loading progress indicator.
                loadingIndicator.IsVisible = false;
            }
            catch (Exception ex)
            {
                await Application.Current.MainPage.DisplayAlert("Alert", ex.ToString(), "OK");
            }
        }

        private async void TakeMapOfflineButton_Click(object sender, EventArgs e)
        {
            // Clean up any previous outputs in the temp directory.
            string tempPath = $"{Path.GetTempPath()}";
            string[] outputFolders = Directory.GetDirectories(tempPath, "NapervilleWaterNetwork*");

            // Loop through the folder names and delete them.
            foreach (string dir in outputFolders)
            {
                try
                {
                    // Delete the folder.
                    Directory.Delete(dir, true);
                }
                catch (Exception)
                {
                    // Ignore exceptions (files might be locked, for example).
                }
            }

            // Create a new folder for the output mobile map.
            string packagePath = Path.Combine(tempPath, @"NapervilleWaterNetwork");
            int num = 1;
            while (Directory.Exists(packagePath))
            {
                packagePath = Path.Combine(tempPath, @"NapervilleWaterNetwork" + num.ToString());
                num++;
            }

            // Create the output directory.
            Directory.CreateDirectory(packagePath);

            try
            {
                // Show the progress indicator while the job is running.
                busyIndicator.IsVisible = true;

                // Create an offline map task with the current (online) map.
                OfflineMapTask takeMapOfflineTask = await OfflineMapTask.CreateAsync(MyMapView.Map);

                // Create the default parameters for the task, pass in the area of interest.
                GenerateOfflineMapParameters parameters = await takeMapOfflineTask.CreateDefaultGenerateOfflineMapParametersAsync(_areaOfInterest);

                // Generate parameter overrides for more in-depth control of the job.
                GenerateOfflineMapParameterOverrides overrides = await takeMapOfflineTask.CreateGenerateOfflineMapParameterOverridesAsync(parameters);

                // Create the UI for configuring the overrides.
                ConfigureOverridesPage configurationPage = new ConfigureOverridesPage(overrides, MyMapView.Map);

                // Wait for the user to finish with the configuration, then continue.
                configurationPage.Disappearing += async (o, args) =>
                {
                    // Re-show the busy indicator.
                    busyIndicator.IsVisible = true;

                    // Create the job with the parameters and output location.
                    _generateOfflineMapJob = takeMapOfflineTask.GenerateOfflineMap(parameters, packagePath, overrides);

                    // Handle the progress changed event for the job.
                    _generateOfflineMapJob.ProgressChanged += OfflineMapJob_ProgressChanged;

                    // Await the job to generate geodatabases, export tile packages, and create the mobile map package.
                    GenerateOfflineMapResult results = await _generateOfflineMapJob.GetResultAsync();

                    // Check for job failure (writing the output was denied, e.g.).
                    if (_generateOfflineMapJob.Status != JobStatus.Succeeded)
                    {
                        await Application.Current.MainPage.DisplayAlert("Alert", "Generate offline map package failed.", "OK");
                        busyIndicator.IsVisible = false;
                    }

                    // Check for errors with individual layers.
                    if (results.LayerErrors.Any())
                    {
                        // Build a string to show all layer errors.
                        System.Text.StringBuilder errorBuilder = new System.Text.StringBuilder();
                        foreach (KeyValuePair<Layer, Exception> layerError in results.LayerErrors)
                        {
                            errorBuilder.AppendLine($"{layerError.Key.Id} : {layerError.Value.Message}");
                        }

                        // Show layer errors.
                        string errorText = errorBuilder.ToString();
                        await Application.Current.MainPage.DisplayAlert("Alert", errorText, "OK");
                    }

                    // Display the offline map.
                    MyMapView.Map = results.OfflineMap;

                    // Apply the original viewpoint for the offline map.
                    MyMapView.SetViewpoint(new Viewpoint(_areaOfInterest));

                    // Enable map interaction so the user can explore the offline data.
                    MyMapView.InteractionOptions.IsEnabled = true;

                    // Hide the "Take map offline" button.
                    takeOfflineArea.IsVisible = false;

                    // Show a message that the map is offline.
                    messageArea.IsVisible = true;

                    // Hide the busy indicator.
                    busyIndicator.IsVisible = false;
                };

                // Show the configuration UI.
                await Navigation.PushModalAsync(configurationPage, true);

            }
            catch (TaskCanceledException)
            {
                // Generate offline map task was canceled.
                await Application.Current.MainPage.DisplayAlert("Alert", "Taking map offline was canceled", "OK");
            }
            catch (Exception ex)
            {
                // Exception while taking the map offline.
                await Application.Current.MainPage.DisplayAlert("Alert", ex.Message, "OK");
            }
            finally
            {
                // Hide the activity indicator when the job is done.
                busyIndicator.IsVisible = false;
            }
        }

        // Show changes in job progress.
        private void OfflineMapJob_ProgressChanged(object sender, EventArgs e)
        {
            // Get the job.
            GenerateOfflineMapJob job = sender as GenerateOfflineMapJob;

            // Dispatch to the UI thread.
            Device.BeginInvokeOnMainThread(() =>
            {
                // Show the percent complete and update the progress bar.
                Percentage.Text = job.Progress > 0 ? job.Progress.ToString() + " %" : string.Empty;
                progressBar.Progress = job.Progress / 100.0;
            });
        }

        private void CancelJobButton_Click(object sender, EventArgs e)
        {
            // The user canceled the job.
            _generateOfflineMapJob.Cancel();
        }

        #region Authentication

        // Constants for OAuth-related values.
        // - The URL of the portal to authenticate with (ArcGIS Online).
        private const string ServerUrl = "https://www.arcgis.com/sharing/rest";

        // - The Client ID for an app registered with the server (the ID below is for a public app created by the ArcGIS Runtime team).
        private const string AppClientId = @"6wMAmbUEX1rvsOb4";

        // - A URL for redirecting after a successful authorization (this must be a URL configured with the app).
        private const string OAuthRedirectUrl = @"forms-samples-app://auth";

        private void SetOAuthInfo()
        {
            // Register the server information with the AuthenticationManager.
            ServerInfo serverInfo = new ServerInfo
            {
                ServerUri = new Uri(ServerUrl),
                TokenAuthenticationType = TokenAuthenticationType.OAuthImplicit,
                OAuthClientInfo = new OAuthClientInfo
                {
                    ClientId = AppClientId,
                    RedirectUri = new Uri(OAuthRedirectUrl)
                }
            };

            // Register this server with AuthenticationManager.
            AuthenticationManager.Current.RegisterServer(serverInfo);

            // Use a function in this class to challenge for credentials.
            AuthenticationManager.Current.ChallengeHandler = new ChallengeHandler(CreateCredentialAsync);

            // Set the OAuthAuthorizeHandler component (this class) for Android or iOS platforms.
#if __ANDROID__ || __IOS__
            AuthenticationManager.Current.OAuthAuthorizeHandler = this;
#endif
        }

        // ChallengeHandler function that will be called whenever access to a secured resource is attempted.
        public async Task<Credential> CreateCredentialAsync(CredentialRequestInfo info)
        {
            Credential credential = null;

            try
            {
                // IOAuthAuthorizeHandler will challenge the user for OAuth credentials.
                credential = await AuthenticationManager.Current.GenerateCredentialAsync(info.ServiceUri);
            }
            catch (TaskCanceledException)
            {
                return credential;
            }
            catch (Exception)
            {
                // Exception will be reported in calling function.
                throw;
            }

            return credential;
        }

        #region IOAuthAuthorizationHandler implementation

        // Use a TaskCompletionSource to track the completion of the authorization.
        private TaskCompletionSource<IDictionary<string, string>> _taskCompletionSource;

        // IOAuthAuthorizeHandler.AuthorizeAsync implementation.
        public Task<IDictionary<string, string>> AuthorizeAsync(Uri serviceUri, Uri authorizeUri, Uri callbackUri)
        {
            // If the TaskCompletionSource is not null, authorization may already be in progress and should be canceled.
            if (_taskCompletionSource != null)
            {
                // Try to cancel any existing authentication task.
                _taskCompletionSource.TrySetCanceled();
            }

            // Create a task completion source.
            _taskCompletionSource = new TaskCompletionSource<IDictionary<string, string>>();
#if __ANDROID__ || __IOS__

#if __ANDROID__
            // Get the current Android Activity.
            Activity activity = (Activity)ArcGISRuntime.Droid.MainActivity.Instance;
#endif
#if __IOS__
            // Get the current iOS ViewController.
            UIViewController viewController = null;
            Device.BeginInvokeOnMainThread(() => { viewController = UIApplication.SharedApplication.KeyWindow.RootViewController; });
#endif
            // Create a new Xamarin.Auth.OAuth2Authenticator using the information passed in.
            OAuth2Authenticator authenticator = new OAuth2Authenticator(
                clientId: AppClientId,
                scope: "",
                authorizeUrl: authorizeUri,
                redirectUrl: callbackUri)
            {
                ShowErrors = false,
                // Allow the user to cancel the OAuth attempt.
                AllowCancel = true
            };

            // Define a handler for the OAuth2Authenticator.Completed event.
            authenticator.Completed += (sender, authArgs) =>
            {
                try
                {
#if __IOS__
                    // Dismiss the OAuth UI when complete.
                    viewController.DismissViewController(true, null);
#endif

                    // Check if the user is authenticated.
                    if (authArgs.IsAuthenticated)
                    {
                        // If authorization was successful, get the user's account.
                        Xamarin.Auth.Account authenticatedAccount = authArgs.Account;

                        // Set the result (Credential) for the TaskCompletionSource.
                        _taskCompletionSource.SetResult(authenticatedAccount.Properties);
                    }
                    else
                    {
                        throw new Exception("Unable to authenticate user.");
                    }
                }
                catch (Exception ex)
                {
                    // If authentication failed, set the exception on the TaskCompletionSource.
                    _taskCompletionSource.TrySetException(ex);

                    // Cancel authentication.
                    authenticator.OnCancelled();
                }
                finally
                {
                    // Dismiss the OAuth login.
#if __ANDROID__
                    activity.FinishActivity(99);
#endif
                }
            };

            // If an error was encountered when authenticating, set the exception on the TaskCompletionSource.
            authenticator.Error += (sndr, errArgs) =>
            {
                // If the user cancels, the Error event is raised but there is no exception ... best to check first.
                if (errArgs.Exception != null)
                {
                    _taskCompletionSource.TrySetException(errArgs.Exception);
                }
                else
                {
                    // Login canceled: dismiss the OAuth login.
                    if (_taskCompletionSource != null)
                    {
                        _taskCompletionSource.TrySetCanceled();
#if __ANDROID__
                        activity.FinishActivity(99);
#endif
                    }
                }

                // Cancel authentication.
                authenticator.OnCancelled();
            };

            // Present the OAuth UI so the user can enter user name and password.
#if __ANDROID__
            Android.Content.Intent intent = authenticator.GetUI(activity);
            activity.StartActivityForResult(intent, 99);
#endif
#if __IOS__
            // Present the OAuth UI (on the app's UI thread) so the user can enter user name and password.
            Device.BeginInvokeOnMainThread(() => { viewController.PresentViewController(authenticator.GetUI(), true, null); });
#endif

#endif // (If Android or iOS)
            // Return completion source task so the caller can await completion.
            return _taskCompletionSource.Task;
        }

        #endregion IOAuthAuthorizationHandler implementation

        #endregion Authentication
    }
}
XAML
<?xml version="1.0" encoding="utf-8" ?>

<ContentPage x:Class="ArcGISRuntimeXamarin.Samples.GenerateOfflineMapWithOverrides.ConfigureOverridesPage" xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" Title="Configure offline map job">
    <Grid Margin="10" VerticalOptions="Center">
        <Grid.Resources>
            <Style TargetType="Label">
                <Setter Property="Margin" Value="0,0,0,5" />
                <Setter Property="VerticalOptions" Value="Center" />
            </Style>
            <Style TargetType="Entry">
                <Setter Property="Margin" Value="0,0,0,5" />
            </Style>
            <Style TargetType="Switch">
                <Setter Property="Margin" Value="0,0,0,5" />
            </Style>
        </Grid.Resources>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="Auto" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Label Grid.Row="0" Grid.Column="0" FontAttributes="Bold" Text="Adjust the basemap" />
        <Label Grid.Row="1" Grid.Column="0" Text="Min scale:" />
        <Slider
            x:Name="MinScaleEntry"
            Grid.Row="1"
            Grid.Column="1"
            Maximum="23"
            MaximumTrackColor="CadetBlue"
            Minimum="0"
            MinimumTrackColor="CadetBlue"
            Value="0" />
        <Label Grid.Row="1" Grid.Column="2" Text="{Binding Source={x:Reference MinScaleEntry}, Path=Value, StringFormat='{0:N0}'}" />
        <Label Grid.Row="2" Grid.Column="0" Text="Max scale:" />
        <Slider
            x:Name="MaxScaleEntry"
            Grid.Row="2"
            Grid.Column="1"
            Maximum="23"
            MaximumTrackColor="CadetBlue"
            Minimum="0"
            MinimumTrackColor="CadetBlue"
            Value="23" />
        <Label Grid.Row="2" Grid.Column="2" Text="{Binding Source={x:Reference MaxScaleEntry}, Path=Value, StringFormat='{0:N0}'}" />
        <Label Grid.Row="3" Grid.Column="0" Text="Buffer distance:" />
        <Slider
            x:Name="ExtentBufferEntry"
            Grid.Row="3"
            Grid.Column="1"
            Maximum="500"
            MaximumTrackColor="CadetBlue"
            Minimum="0"
            MinimumTrackColor="CadetBlue"
            Value="250" />
        <Label Grid.Row="3" Grid.Column="2" Text="{Binding Source={x:Reference ExtentBufferEntry}, Path=Value, StringFormat='{0:N0}m'}" />
        <Label Grid.Row="4" Grid.Column="0" FontAttributes="Bold" Text="Choose layers" />
        <Label Grid.Row="5" Grid.Column="0" Text="System valves:" />
        <Switch x:Name="SysValvesLayerCheckbox" Grid.Row="5" Grid.Column="2" IsToggled="True" />
        <Label Grid.Row="6" Grid.Column="0" Text="Service connections:" />
        <Switch x:Name="ServiceConnCheckbox" Grid.Row="6" Grid.Column="2" />
        <Label
            Grid.Row="7"
            Grid.Column="0"
            Margin="0,0,0,5"
            FontAttributes="Bold"
            Text="Apply a feature layer filer" />
        <Label Grid.Row="8" Grid.Column="0" Margin="0,0,5,5" Text="Min Flow Rate:" />
        <Slider
            x:Name="FlowRateFilterEntry"
            Grid.Row="8"
            Grid.Column="1"
            Maximum="999"
            MaximumTrackColor="CadetBlue"
            Minimum="0"
            MinimumTrackColor="CadetBlue"
            Value="500" />
        <Label Grid.Row="8" Grid.Column="2" MinimumWidthRequest="40" Text="{Binding Source={x:Reference FlowRateFilterEntry}, Path=Value, StringFormat='{0:N0} GPM'}" />
        <Label Grid.Row="9" Grid.Column="0" FontAttributes="Bold" Text="Crop layers to extent" />
        <Label Grid.Row="10" Grid.Column="0" Text="Water pipes:" />
        <Switch x:Name="CropLayerCheckbox" Grid.Row="10" Grid.Column="2" IsToggled="True" />
        <Button
            Grid.Row="11"
            Grid.Column="0"
            Grid.ColumnSpan="3"
            Clicked="TakeMapOffline_Clicked"
            IsEnabled="True"
            Text="Take map offline" />
    </Grid>
</ContentPage>
XAML
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"             
             xmlns:esriUI="clr-namespace:Esri.ArcGISRuntime.Xamarin.Forms;assembly=Esri.ArcGISRuntime.Xamarin.Forms"
             xmlns:mapping="clr-namespace:Esri.ArcGISRuntime.Mapping;assembly=Esri.ArcGISRuntime" 
             x:Class="ArcGISRuntimeXamarin.Samples.GenerateOfflineMapWithOverrides.GenerateOfflineMapWithOverrides">
  <Grid>
        <esriUI:MapView x:Name="MyMapView"/>

        <Frame x:Name="takeOfflineArea" 
               BackgroundColor="White" BorderColor="Black"
               HorizontalOptions="End" VerticalOptions="Start"
               Margin="30" Padding="20" WidthRequest="375">
            <StackLayout HorizontalOptions="Center" VerticalOptions="Center">
                <Button x:Name="TakeMapOfflineButton" 
                        WidthRequest="250"
                        Clicked="TakeMapOfflineButton_Click" 
                        IsEnabled="True" 
                        Text="Take map offline"/>
            </StackLayout>
        </Frame>

        <Frame x:Name="messageArea" 
               IsVisible="False"
               BackgroundColor="White" BorderColor="Black"
               HorizontalOptions="End" VerticalOptions="Start"
               Margin="30" Padding="5" WidthRequest="450">
            <StackLayout>
                <Label HorizontalOptions="Center" VerticalOptions="Center" Margin="10"
                       Text="Map is offline!"/>
            </StackLayout>
        </Frame>

        <Grid x:Name="busyIndicator" BackgroundColor="#807f7f7f" IsVisible="False">
            <Grid HorizontalOptions="Center" VerticalOptions="Center">
                <Grid.RowDefinitions>
                    <RowDefinition Height="auto"/>
                    <RowDefinition Height="auto"/>
                    <RowDefinition Height="auto"/>
                </Grid.RowDefinitions>
                <Label HorizontalOptions="Center" VerticalOptions="Center" Margin="10"
                       TextColor="White" FontSize="18">
                    <Label.FormattedText>
                        <FormattedString>
                            <Span Text="Generating offline map... "/>
                            <Span x:Name="Percentage" Text=""/>
                        </FormattedString>
                    </Label.FormattedText>
                </Label>
                <ProgressBar x:Name="progressBar"
                             Grid.Row="1"
                             IsEnabled="True"
                             HorizontalOptions="Center" VerticalOptions="Center" 
                             WidthRequest="100" HeightRequest="10" Margin="0,0,0,10"/>
                <Button x:Name="CancelJobButton" 
                        Grid.Row="3"
                        Text="Cancel" 
                        Clicked="CancelJobButton_Click" 
                        HorizontalOptions="Center" 
                        WidthRequest="100"/>
            </Grid>
        </Grid>
        <Grid x:Name="loadingIndicator" 
              BackgroundColor="#807f7f7f" 
              IsVisible="True">
            <Grid HorizontalOptions="Center" VerticalOptions="Center">
                <Grid.RowDefinitions>
                    <RowDefinition Height="auto"/>
                    <RowDefinition Height="auto"/>
                </Grid.RowDefinitions>
                <Label Text="Loading online map..." 
                           TextColor="White" FontSize="18"
                           Margin="10"/>
                <ProgressBar Grid.Row="1"
                             IsEnabled="True"
                             WidthRequest="100" HeightRequest="10" 
                             HorizontalOptions="Center" VerticalOptions="Center"/>
            </Grid>
        </Grid>
    </Grid>
</ContentPage>
See Also
Additional Examples
Hyperlink to ExampleDescription
GenerateOfflineMapWithOverridesTake a web map offline with additional options for each layer.