I have a Forms app that takes a few seconds to populate data when I click on a viewCell.
Is there a way that I can show a circular busy indicator during this time through custom renderers or something like that?
4 Answers
Answers 1
You can implement the same by using ActivityIndicator control.
If you are expecting have busy-indicators on multiple pages, then would recommend to implement this using the ControlTemplate (it also allows you to define overlays if needed).
Page Template
<Application.Resources> <ResourceDictionary> <ControlTemplate x:Key="DefaultTemplate"> <Grid> <!-- page content --> <ContentPresenter /> <!-- overlay --> <BoxView BackgroundColor="Black" Opacity="0.5" IsVisible="{TemplateBinding BindingContext.IsBusy}"/> <!-- busy indicator with text --> <Frame HorizontalOptions="Center" VerticalOptions="Center" IsVisible="{TemplateBinding BindingContext.IsBusy}"> <StackLayout> <ActivityIndicator IsRunning="{TemplateBinding BindingContext.IsBusy}" /> <Label Text="{TemplateBinding BindingContext.BusyText}" /> </StackLayout> </Frame> </Grid> </ControlTemplate> </ResourceDictionary> </Application.Resources> Sample usage:
XAML - assign template to page
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" ControlTemplate="{StaticResource DefaultTemplate}" .. > .... </ContentPage> View Model
public class BaseViewModel : ObservableObject { bool _isBusy; public bool IsBusy { get => _isBusy; set => SetProperty(ref _isBusy, value); } string _busyText = "loading.."; public string BusyText { get => _busyText; set => SetProperty(ref _busyText, value); } } public class TestViewModel : BaseViewModel { public ICommand OnTapCommand { get => new Command(async (obj) => { IsBusy = true; //do heavy lifting here await Task.Delay(2000); IsBusy = false; }); } ... Answers 2
You can use Acr.UserDialogs, it's a cross-platform package with busy indicators, dialogs, toasts, etc.
In your case, you need to use Loading.
using (Acr.UserDialogs.UserDialogs.Instance.Loading("your message here")) { //your long task here } For example...
Answers 3
I accomplished this by creating an activity indicator control that can be used in my entire app. I even made it so that you can change the activity indicator text to show any text that you want such as 'Logging in', 'loading', 'uploading', etc. See my post below. Let me know if you have any questions.
Is it possible to have one Activity indicator for entire app?
Answers 4
you can use a DependencyService to Show and Hide a loading indicator.
You will have to download AndHUD for android and BTProgressHUD for iOS NuGet packages.
DependencyService interface
using MyApp.Helpers; namespace MyApp { interface IHudService { void ShowHud(string ProgressText = StaticData.Loading); void HideHud(); void SetText(string Text); void SetProgress(double Progress, string ProgressText = ""); } } Android Code
using AndroidHUD; using Android.Views; using Xamarin.Forms; using MyApp.Droid; using MyApp.DependencyServices; using MyApp.Helpers; [assembly: Dependency(typeof(DroidHudService))] namespace MyApp.Droid { public class DroidHudService : IHudService { #region IHudManager implementation bool isHudVisible; public void ShowHud(string ProgressText = StaticData.Loading) { Device.BeginInvokeOnMainThread(() => { AndHUD.Shared.Show(Forms.Context, ProgressText, maskType: MaskType.Black); isHudVisible = true; }); } public void HideHud() { Device.BeginInvokeOnMainThread(() => { AndHUD.Shared.Dismiss(Forms.Context); isHudVisible = false; }); } public void SetProgress(double Progress, string ProgressText = "") { if (!isHudVisible) return; Device.BeginInvokeOnMainThread(() => { int progress = (int)(Progress * 100); AndHUD.Shared.Show(Forms.Context, ProgressText + progress + "%", progress, MaskType.Black); }); } public void SetText(string Text) { if (!isHudVisible) return; Device.BeginInvokeOnMainThread(() => { AndHUD.Shared.Show(Forms.Context, Text, maskType: MaskType.Black); }); } Android.Views.View CustomLoadingView(string ProgressText) { Android.Views.View loadingView = new Android.Views.View(Forms.Context); return loadingView; } #endregion } } iOS Code
using System; using BigTed; using CoreAnimation; using CoreGraphics; using MyApp.DependencyServices; using MyApp.Helpers; using MyApp.iOS; using Foundation; using UIKit; using Xamarin.Forms; [assembly: Dependency(typeof(IosHudService))] namespace MyApp.iOS { public class IosHudService : IHudService { UIView _load; bool isHudVisible; #region IHudManager implementation public void ShowHud(string ProgressText = StaticData.Loading) { isHudVisible = true; SetText(ProgressText); } public void HideHud() { Device.BeginInvokeOnMainThread(() => { BTProgressHUD.Dismiss(); if (_load != null) _load.Hidden = true; isHudVisible = false; }); } public void SetProgress(double Progress, string ProgressText = "") { int progress = (int)(Progress * 100); string text = ProgressText + progress + "%"; SetText(text); } public void SetText(string text) { if (!isHudVisible) return; Device.BeginInvokeOnMainThread(() => { BTProgressHUD.Show(status: text, maskType: ProgressHUD.MaskType.Black); try { lblTitle.Text = text; UIView[] subView = ProgressHUD.Shared.Subviews; for (int i = 0; i < subView.Length; i++) { subView[i].Hidden = true; } _load.Hidden = false; ProgressHUD.Shared.BringSubviewToFront(_load); } catch (Exception ex) { Console.WriteLine("IosHudService.cs - SetText() " + ex.Message); } }); } UILabel lblTitle; UIView CustomLoadingView(string ProgressText) { UIView loadingView = new UIView(); loadingView.Frame = new CGRect(0, 0, UIScreen.MainScreen.Bounds.Width, UIScreen.MainScreen.Bounds.Height); UIImageView imgBg = new UIImageView(); imgBg.Image = UIImage.FromFile("load_bg.png"); imgBg.Frame = new CGRect((loadingView.Frame.Width / 2) - 65, (loadingView.Frame.Height / 2) - 70, 130, 140); loadingView.Add(imgBg); UIImageView someImageView = new UIImageView(); someImageView.Frame = new CGRect((loadingView.Frame.Width / 2) - 40, (loadingView.Frame.Height / 2) - 50, 75, 75); someImageView.AnimationImages = new UIImage[] { UIImage.FromBundle("spinner.png"), }; someImageView.AnimationRepeatCount = nint.MaxValue; // Repeat forever. someImageView.AnimationDuration = 1.0; // Every 1s. someImageView.StartAnimating(); CABasicAnimation rotationAnimation = new CABasicAnimation(); rotationAnimation.KeyPath = "transform.rotation.z"; rotationAnimation.To = new NSNumber(Math.PI * 2); rotationAnimation.Duration = 1; rotationAnimation.Cumulative = true; rotationAnimation.RepeatCount = float.PositiveInfinity; someImageView.Layer.AddAnimation(rotationAnimation, "rotationAnimation"); loadingView.Add(someImageView); lblTitle = new UILabel(); lblTitle.Text = ProgressText; lblTitle.Frame = new CGRect(imgBg.Frame.X, someImageView.Frame.Y + someImageView.Frame.Height + 15, 130, 20); lblTitle.TextAlignment = UITextAlignment.Center; lblTitle.TextColor = UIColor.White; lblTitle.AdjustsFontSizeToFitWidth = true; loadingView.Add(lblTitle); return loadingView; } #endregion } } Show/Hide via DependencyService Method
public static void ShowLoadingIndicator(string progressText = "Loading...") { Device.BeginInvokeOnMainThread(() => { DependencyService.Get<IHudService>().ShowHud(progressText); }); } public static void HideLoadingIndicator() { Device.BeginInvokeOnMainThread(() => { DependencyService.Get<IHudService>().HideHud(); }); } I have manage my code by Creating Disposable class and use it in ViewModels like this:
public class Busy : IDisposable { readonly object _sync = new object(); readonly BaseViewModel _viewModel; readonly bool _showProgressView; public Busy(BaseViewModel viewModel, bool showProgressView, string displayMessage = null) { try { _viewModel = viewModel; lock (_sync) { _viewModel.IsBusy = true; _showProgressView = showProgressView; if (_showProgressView) { if (string.IsNullOrEmpty(displayMessage)) { displayMessage = "Loading..."; } DependencyService.Get<IHudService>().ShowHud(displayMessage); } } } catch(Exception ex) { ex.Track(); } } public void Dispose() { try { lock (_sync) { _viewModel.IsBusy = false; if (_showProgressView) { DependencyService.Get<IHudService>().HideHud(); } } } catch(Exception ex) { ex.Track(); } } } Show the loader indicater via using instance of Busy class Like this:
using (new Busy(this, true)) { //Your api or waiting stuff } 

0 comments:
Post a Comment