Skip to content

Instantly share code, notes, and snippets.

@Cheesebaron
Last active January 23, 2016 07:05
Show Gist options
  • Save Cheesebaron/ddec891e5eedfcf3db75 to your computer and use it in GitHub Desktop.
Save Cheesebaron/ddec891e5eedfcf3db75 to your computer and use it in GitHub Desktop.
public sealed class Snackbar : UIView
{
public TimeSpan Duration { get; set; }
public Action<Snackbar> OnAction { get; set; }
public Action<Snackbar> OnDismiss { get; set; }
private static Snackbar _currentlyVisibleSnackbar;
private readonly UILabel _messageLabel;
private readonly UIButton _actionButton;
private readonly UIView _separator;
private NSLayoutConstraint[] _horizontalLayoutConstraints;
private NSLayoutConstraint[] _visibleVerticalLayoutConstraints;
private NSLayoutConstraint[] _hiddenVerticalLayoutConstraints;
private NSTimer _dismissalTimer;
private bool _actionDispatched;
public static Snackbar WithMessage(string message, string actionText, TimeSpan duration,
Action<Snackbar> onAction, Action<Snackbar> onDismiss)
{
var snackBar = new Snackbar(message, actionText, duration, onAction, onDismiss);
return snackBar;
}
private Snackbar(string message, string actionText, TimeSpan duration,
Action<Snackbar> onAction, Action<Snackbar> onDismiss) : base(new CGRect(0,0,0,0))
{
TranslatesAutoresizingMaskIntoConstraints = false;
Duration = duration;
OnAction = onAction;
OnDismiss = onDismiss;
_messageLabel = new UILabel {
Text = message,
TranslatesAutoresizingMaskIntoConstraints = false,
Font = UIFont.SystemFontOfSize(14f),
TextColor = UIColor.White
};
_messageLabel.SizeToFit();
_actionButton = new UIButton(UIButtonType.System) {
TranslatesAutoresizingMaskIntoConstraints = false
};
_actionButton.TitleLabel.Font = UIFont.SystemFontOfSize(14f, UIFontWeight.Bold);
_actionButton.SetTitle(actionText, UIControlState.Normal);
_actionButton.SetTitleColor(UIColor.White, UIControlState.Normal);
_actionButton.SizeToFit();
_actionButton.TouchUpInside += ExecuteAction;
_separator = new UIView {
BackgroundColor = UIColor.FromWhiteAlpha(0.99f, 0.1f),
TranslatesAutoresizingMaskIntoConstraints = false
};
AddSubview(_messageLabel);
AddSubview(_actionButton);
AddSubview(_separator);
Opaque = false;
}
public override void Draw(CGRect rect)
{
base.Draw(rect);
using (var context = UIGraphics.GetCurrentContext())
{
context.SaveState();
UIColor.FromWhiteAlpha(0.1f, 0.9f).SetFill();
var clippath = UIBezierPath.FromRoundedRect(rect, 3f);
clippath.Fill();
context.RestoreState();
}
}
public void Show()
{
var topController = UIApplication.SharedApplication.KeyWindow.RootViewController;
while (topController?.PresentedViewController != null) {
topController = topController?.PresentedViewController;
}
if (topController == null)
{
Console.WriteLine("I have no top controller, I am drunk...");
return;
}
var superView = topController.View;
var shouldReplaceExistingSnackbar = _currentlyVisibleSnackbar != null;
if (shouldReplaceExistingSnackbar)
{
_currentlyVisibleSnackbar.InvalidateTimer();
_currentlyVisibleSnackbar.Dismiss(false);
}
superView.AddSubview(this);
superView.AddConstraints(HorizontalLayoutConstraints);
superView.AddConstraints(shouldReplaceExistingSnackbar
? VisibleVerticalLayoutConstraints
: HiddenVerticalLayoutConstraints);
superView.LayoutIfNeeded();
SetupContentLayout();
if (!shouldReplaceExistingSnackbar)
{
superView.RemoveConstraints(HiddenVerticalLayoutConstraints);
superView.AddConstraints(VisibleVerticalLayoutConstraints);
Animate(0.2, 0, UIViewAnimationOptions.CurveEaseOut,
() => superView.LayoutIfNeeded(), () => { });
}
_dismissalTimer = NSTimer.CreateScheduledTimer(Duration, TimeoutForDismissal);
_currentlyVisibleSnackbar = this;
}
private void TimeoutForDismissal(NSTimer nsTimer)
{
Dismiss();
}
private void Dismiss(bool animated = true)
{
InvalidateTimer();
Superview.RemoveConstraints(VisibleVerticalLayoutConstraints);
Superview.AddConstraints(HiddenVerticalLayoutConstraints);
_currentlyVisibleSnackbar = null;
if (!animated)
{
ExecuteDismissal();
RemoveFromSuperview();
}
else
{
Animate(0.2, 0, UIViewAnimationOptions.CurveEaseIn,
() => Superview.LayoutIfNeeded(),
() => {
if (!_actionDispatched)
ExecuteDismissal();
RemoveFromSuperview();
});
}
}
private void InvalidateTimer()
{
_dismissalTimer?.Invalidate();
_dismissalTimer?.Dispose();
_dismissalTimer = null;
}
private void SetupContentLayout()
{
var constraints = new List<NSLayoutConstraint>();
constraints.AddRange(
NSLayoutConstraint.FromVisualFormat(
"|-8-[label]-(>=8)-[sep(1)]-8-[button]-8-|",
NSLayoutFormatOptions.AlignAllCenterY,
"label", _messageLabel, "button", _actionButton, "sep", _separator));
constraints.AddRange(
NSLayoutConstraint.FromVisualFormat(
"V:|-0-[sep]-0-|", 0, "sep", _separator));
constraints.Add(NSLayoutConstraint.Create(_messageLabel, NSLayoutAttribute.CenterY,
NSLayoutRelation.Equal, this, NSLayoutAttribute.CenterY, 1, 0));
AddConstraints(constraints.ToArray());
LayoutIfNeeded();
}
private NSLayoutConstraint[] HorizontalLayoutConstraints =>
_horizontalLayoutConstraints ?? (_horizontalLayoutConstraints =
NSLayoutConstraint.FromVisualFormat(@"H:|-5-[bar]-(5)-|", 0, "bar", this));
private NSLayoutConstraint[] VisibleVerticalLayoutConstraints =>
_visibleVerticalLayoutConstraints ?? (_visibleVerticalLayoutConstraints =
NSLayoutConstraint.FromVisualFormat(@"V:[bar(44)]-(5)-|", 0, "bar", this));
private NSLayoutConstraint[] HiddenVerticalLayoutConstraints =>
_hiddenVerticalLayoutConstraints ?? (_hiddenVerticalLayoutConstraints =
NSLayoutConstraint.FromVisualFormat(@"V:[bar(44)]-(-50)-|", 0, "bar", this));
private void ExecuteAction(object sender, EventArgs eventArgs)
{
InvalidateTimer();
ExecuteAction();
Dismiss();
}
private void ExecuteAction()
{
_actionDispatched = true;
OnAction?.Invoke(this);
}
private void ExecuteDismissal()
{
if (!_actionDispatched)
OnDismiss?.Invoke(this);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment