Programming a Snackbar into Your iOS App

Monday Aug 28th 2017 by Uma Narayanan
Share:

Snackbar is a default control available in Xamarin Android. It is useful to display in-app notifications. Learn to create a snackbar for iOS.

Introduction

Snackbar is a default control available in Xamarin Android. It is useful when you need to display in-app notifications. However, we don't have an equivalent control in iOS. In this article, we will create a custom snackbar for iOS.

Description

To begin with, we will create a Xamarin Forms application with the target platform as iOS. Because Snackbar is a platform-specific feature, we will use a 'Dependency' service to communicate between the forms and the platform-specific project. To use dependency injection, we will use FreshMVVM.

Xamarin Forms Level Changes

In the Xamarin Forms project, add a new interface named "IShareInfo.cs" with the following definition:

public interface IShareInfo
{
   Task ShareText(string message);
}

The method 'ShareText' will be defined in the platform-specific project.

Add the 'FreshMVVM' NuGet package to the project. In the constructor method of App.Xaml.cs, register and resolve the dependency service—IShareInfo—as shown next:

FreshIOC.Container.Register(DependencyService.Get<IShareInfo>());
var shareService = FreshIOC.Container.Resolve<IShareInfo>();

This dependency service will be injected in the <xaml>.cs page as a parameter in the constructor. In this example, a page named 'NotificationAppPage.xaml' is already created and the page is set as a main page in App.xaml.cs:

MainPage = new NotificationAppPage(shareService);

In NotificationAppPage.xaml.cs, the constructor is defined as shown below:

IShareInfo _shareInfo;
public NotificationAppPage(IShareInfo shareInfo)
{
   InitializeComponent();
   _shareInfo = shareInfo;
}

In the XAML project, add a button and, in the button click event handler, call the IShareInfo's ShareText method:

_shareInfo.ShareText("Hello Desc");

This completes the required changes in the forms app.

iOS Changes

Snackbar Utility

In the iOS project, add a new class file named 'SnackbarUtility.cs.' In this file, create a class named 'SnackbarViewSample' that inherits UIView. The class definition is as shown below:

public class SnackbarViewSample : UIView
{
   ...
}

In the SnackbarViewSample class, declare four objects as depicted below:

private UIImageView iconCircleView;
private UIImageView iconImageView;
private UILabel titleLabel;
private UILabel descLabel
public bool IsShowing { get; private set; }

Two image view objects display the image within an outer circle image. A label to display the title and a label to display the description are included. IsShowing is a flag that is used to hide the snackbar after a certain duration.

We define a method named "ShowSanckbar" as defined below:

 1. public void ShowSnackbar(UIColor bgColor, UIImage icon,
       string title, string desc, long displayDuration)
    {
 2.    BackgroundColor = bgColor;
 3.    iconImageView.Image = icon.ImageWithRenderingMode
          (UIImageRenderingMode.AlwaysTemplate)
 4.    titleLabel.Text = title;
 5.    descLabel.Text = desc;

 6.    if (!IsShowing)
       {
 7.       UIViewController drawerViewController =
             AppDelegate.GetVisibleViewController();

 8.       if (drawerViewController.PresentedViewController
             != null)
          {
             drawerViewController =
                drawerViewController.PresentedViewController;
          }

          drawerViewController.View.AddSubview(this);

 9.       if (leftConstriant != null)
          {
             leftConstriant.Active = false;
             RemoveConstraint(leftConstriant);
          }



10.       if (rightConstriant != null)
          {
             rightConstriant.Active = false;
             RemoveConstraint(rightConstriant);
          }
11.       leftConstriant = NSLayoutConstraint.Create(this,
             NSLayoutAttribute.Left,
             NSLayoutRelation.Equal, drawerViewController.View,
             NSLayoutAttribute.Left, 1.0f, 0.0f);
          rightConstriant = NSLayoutConstraint.Create(this,
             NSLayoutAttribute.Right,
             NSLayoutRelation.Equal, drawerViewController.View,
             NSLayoutAttribute.Right, 1.0f, 0.0f);
          leftConstriant.Active = true;
          rightConstriant.Active = true;
          var height = 100;

          if (heightConstriant != null)
          {
             heightConstriant.Active = false;
             RemoveConstraint(heightConstriant);
          }

          heightConstriant = NSLayoutConstraint.Create(this,
             NSLayoutAttribute.Height,
             NSLayoutRelation.Equal, null,
             NSLayoutAttribute.NoAttribute, 1.0f, height);
          heightConstriant.Active = true;

          if (topConstriant != null)
          {
             topConstriant.Active = false;
             RemoveConstraint(topConstriant);
          }

          topConstriant = NSLayoutConstraint.Create(this,
             NSLayoutAttribute.Top,
             NSLayoutRelation.Equal, drawerViewController.View,
             NSLayoutAttribute.Top, 1.0f, -height - HeightOffset);
          topConstriant.Active = true;

12.    DispatchQueue.MainQueue.DispatchAfter(new
             DispatchTime(DispatchTime.Now, ShortDelay),
          () =>
          {
             topConstriant.Constant = 0.0f;
             UIView.Animate(AnimateDuration,
                0.0f,
                   UIViewAnimationOptions.LayoutSubviews |
                         UIViewAnimationOptions.CurveEaseIn,
                   () =>
                   {
                      drawerViewController.View.LayoutIfNeeded();
                  },
                  () =>
                  {
                     topConstriant.Constant = TopConstantExtra;
                        UIView.Animate(UpAnimationTime,
                           0.0f,
                     UIViewAnimationOptions.LayoutSubviews |
                     UIViewAnimationOptions.CurveEaseOut,
                  () =>
                  {
                     drawerViewController.View.LayoutIfNeeded();
                  },
                  () =>
                  {
                     if (displayDuration > 0)
                     {
                        DispatchQueue.MainQueue.DispatchAfter(
                           new DispatchTime(DispatchTime.Now,
                           (long)displayDuration *
                              NanosecondsPerSecond),
                           () => HideSnackbar());
                     }
                  });
               });
            });

The snackbar method takes parameters for the background color, icon, title, and description to be displayed on the snackbar. In Line 7, we get the active view controller on which the snackbar is displayed. In Line 8, we add the current view to the active view controller.

Lines 9-11 set the view's coordinates—the left constraints, right constraints, and the top constraints. At Line 12, we use a dispatcher to display the view with animation and, after a certain duration, the view closes.

The view is hidden by using the method 'HideSnackBar.' The definition is:

public void HideSnackbar()
{
   if (IsShowing)
   {
      topConstriant.Constant = -Bounds.Height;

      UIView.Animate(AnimateDuration,
      () =>
      {
         Superview.LayoutIfNeeded();
         LayoutIfNeeded();
      },
      () =>
      {
         RemoveFromSuperview();
         IsShowing = false;
      });
   }

}

The main logic to create the snackbar is defined in a method called "ConfigureViews," which is called from the constructor of the "SnackbarSampleView." The method definition is as demonstrated below:

1. private void ConfigureViews()
   {
      Layer.ShadowColor = UIColor.Black.CGColor;
      Layer.ShadowOpacity = 0.8f;
      Layer.ShadowRadius  = 4.0f;
      Layer.ShadowOffset = new CoreGraphics.CGSize(2.5f, 2.5f);

      var constraints = new List<NSLayoutConstraint>();

      var circle = UIImage.FromBundle("circleOutline").
         ImageWithRenderingMode(UIImageRenderingMode.
            AlwaysTemplate);
      iconCircleView = new UIImageView(circle)
      {
         TranslatesAutoresizingMaskIntoConstraints = false,
         BackgroundColor = UIColor.Clear,
         TintColor = UIColor.Blue
      };
      AddSubview(iconCircleView);

   var left = NSLayoutConstraint.Create(iconCircleView,
      NSLayoutAttribute.Left,
      NSLayoutRelation.Equal, this,
      NSLayoutAttribute.Left, 1.0f, IconViewLeft);
   var top = NSLayoutConstraint.Create(iconCircleView,
      NSLayoutAttribute.Top, NSLayoutRelation.Equal, this,
      NSLayoutAttribute.Top, 1.0f, IconViewTop);
   var width = NSLayoutConstraint.Create(iconCircleView,
      NSLayoutAttribute.Width, NSLayoutRelation.Equal, null,
      NSLayoutAttribute.NoAttribute, 1.0f, IconViewWidth);
   var height = NSLayoutConstraint.Create(iconCircleView,
      NSLayoutAttribute.Height, NSLayoutRelation.Equal, null,
      NSLayoutAttribute.NoAttribute, 1.0f, IconViewHeight);
   constraints.AddRange(new[] { left, top, width, height });

2. iconImageView = new UIImageView
   {
      TranslatesAutoresizingMaskIntoConstraints = false,
      BackgroundColor = UIColor.Clear
   };

   iconCircleView.AddSubview(iconImageView);

   left = NSLayoutConstraint.Create(iconImageView,
      NSLayoutAttribute.CenterX,
      NSLayoutRelation.Equal, iconCircleView,
      NSLayoutAttribute.CenterX, 1.0f, 0.0f);
   top  = NSLayoutConstraint.Create(iconImageView,
      NSLayoutAttribute.CenterY,
      NSLayoutRelation.Equal, iconCircleView,
      NSLayoutAttribute.CenterY, 1.0f, 0.0f);
   width = NSLayoutConstraint.Create(iconImageView,
      NSLayoutAttribute.Width, NSLayoutRelation.Equal, null,
      NSLayoutAttribute.NoAttribute, 1.0f,
         IconImageViewDimensions);
   height = NSLayoutConstraint.Create(iconImageView,
      NSLayoutAttribute.Height, NSLayoutRelation.Equal, null,
      NSLayoutAttribute.NoAttribute, 1.0f,
         IconImageViewDimensions);
   constraints.AddRange(new[] { left, top, width, height });
3. titleLabel = new UILabel
   {
      TranslatesAutoresizingMaskIntoConstraints = false,
      BackgroundColor = UIColor.Clear,
      TextColor = UIColor.Black
   };
   AddSubview(titleLabel);

   left = NSLayoutConstraint.Create(titleLabel,
      NSLayoutAttribute.Left,
      NSLayoutRelation.Equal, iconCircleView,
      NSLayoutAttribute.Right, 1.0f, TitleLabelLeft);
   top  = NSLayoutConstraint.Create(titleLabel,
      NSLayoutAttribute.Top,
      NSLayoutRelation.Equal, iconCircleView,
      NSLayoutAttribute.Top, 1.0f, 0.0f);
   var bottom = NSLayoutConstraint.Create(titleLabel,
      NSLayoutAttribute.Height, NSLayoutRelation.Equal, null,
      NSLayoutAttribute.NoAttribute,  1.0f, TitleLabelBottom);
   var right = NSLayoutConstraint.Create(titleLabel,
      NSLayoutAttribute.Right, NSLayoutRelation.Equal, this,
      NSLayoutAttribute.Right, 1.0f, -TitleLabelRight);

   constraints.AddRange(new[] { left, top, bottom, right });
4. descLabel = new UILabel
   {
      TranslatesAutoresizingMaskIntoConstraints = false,
      BackgroundColor = UIColor.Clear,
      TextColor = UIColor.Blue,
      Lines = 0,
      LineBreakMode = UILineBreakMode.WordWrap
   };

   AddSubview(descLabel);

   left = NSLayoutConstraint.Create(descLabel,
      NSLayoutAttribute.Left, NSLayoutRelation.Equal, titleLabel,
      NSLayoutAttribute.Left, 1.0f, 0.0f);
   top = NSLayoutConstraint.Create(descLabel,
      NSLayoutAttribute.Top, NSLayoutRelation.Equal, titleLabel,
      NSLayoutAttribute.Bottom, 1.0f, DescLabelTop);
   bottom = NSLayoutConstraint.Create(descLabel,
      NSLayoutAttribute.Height,
      NSLayoutRelation.GreaterThanOrEqual, null,
      NSLayoutAttribute.NoAttribute, 1.0f, DescLabelBottom);
   right = NSLayoutConstraint.Create(descLabel,
      NSLayoutAttribute.Right, NSLayoutRelation.Equal, titleLabel,
      NSLayoutAttribute.Right, 1.0f, 0.0f);
   constraints.AddRange(new[] { left, top, bottom, right });


      NSLayoutConstraint.ActivateConstraints
         (constraints.ToArray());
   }

In the configureViews method, we create the different controls and then and add them to the view. At Line 1, we create an outer circle and add it to the view. In Line 2, we add the image provided as a parameter, as a sub view to the circle. Then, we add the title and, finally, the description.

Now, we add a new class, named 'SnackbarUtility;' this class will expose the method to show the snackbar. The class definition is as follows:

public class SnackbarUtility
{

   private readonly SnackbarViewSample toastView;

   public SnackbarUtility()
   {
      toastView = new SnackbarViewSample()
      {
         TranslatesAutoresizingMaskIntoConstraints = false
      };
   }


   public void ShowToast(int timeout,
      UIColor backgroundColor,
      UIImage icon,
      UIColor iconColor,
      string title,
      string description)
   {

      if (toastView.IsShowing)
      {
         toastView.HideSnackbar();
      }
      else
      {
         toastView.ShowSnackbar(backgroundColor,icon, title,
            description, timeout);

      }
   }
}

It has a public method named ShowToast that displays the snackbar.

Dependency Implementation

In the iOS project, we add a class file named 'ShareInfo.cs.' It is the implementation for the 'IShareInfo' service created in the forms project. We decorate the namespace with the following:

[assembly: Xamarin.Forms.Dependency(typeof(ShareInfo))]
namespace NotificationApp.iOS
{
   public class ShareInfo: IShareInfo
   ....

The following method, 'ShareTextData,' displays the message in a snackbar:

1. public async Task ShareTextData(string message)
   {
      UIImage image = UIImage.FromBundle("notificationOutline");
      SnackbarUtility toast = new SnackbarUtility();
      toast.ShowToast(10, UIColor.Yellow, image, UIColor.Blue,
         "Hello toast message", message);
   }

AppDelegate Changes

In the app delegate, add the following method to get the active controller.

UIViewController GetVisibleViewController(UIViewController
   controller = null)
{
   controller = controller ??
   UIApplication.SharedApplication.KeyWindow.RootViewController;
   return controller;
}

When the 'ShareTextData' method is invoked from the Xamarin Forms button click event, it in turn calls the SnackbarUtility's ShowToast message and it displays as follows:

The ShowToast message is displayed
Figure 1: The ShowToast message is displayed

Summary

In this article, we saw how to display a snackbar message on iOS. We created a custom view in Xamarin.iOS. We also connected from Xamarin Forms to the iOS project by using dependency services.

Share:
Home
Mobile Site | Full Site
Copyright 2017 © QuinStreet Inc. All Rights Reserved