开发者

Growl/toast style notifications library for iOS

Can anyone recommend a library for implementing growl or toast-style notifications on iOS? For example, after a user saves a profile, I wan开发者_C百科t to have a notification fade in, linger for 3 seconds, report "profile saved", and fade out. Right now I have a UIAlertView that interrupts the user's workflow with a single "OK" button, and I feel like that is overkill.

The Android Toast class is an example of what I am looking for on iOS.

Thanks!


I created a solution that I think you'll find useful: https://github.com/scalessec/toast

It's written as a obj-c category, essentially adding makeToast methods to any instance of UIView. eg:

[self.view makeToast:@"Profile saved"
            duration:2.0
            position:@"bottom"];


I solved it this way:

  1. Create common label on your view. Make it all screen wide, give it the size you will need and center text in it.
  2. Set it's position "on top" - this label must be below all of your controls in the list of controls.
  3. Add it to interface, properties, synthesize (let's call it "toastLabel" there).
  4. Associate in your XIB file with "toastLabel"
  5. Add following line to your viewWillAppear to hide label for beginning:

    [toastLabel setHidden:TRUE];
    
  6. Add the following code on Button click (or some other event):

    toastLabel.text = @"Our toast text";
    [toastLabel setHidden:TRUE];
    [toastLabel setAlpha:1.0];
    CGPoint location;
    location.x = 160; 
    location.y = 220; 
    toastLabel.center = location;
    location.x = 160; 
    location.y = 320; 
    [toastLabel setHidden:FALSE];
    [UIView animateWithDuration:0.9 animations:^{
        toastLabel.alpha = 0.0;
        toastLabel.center = location;
    }];
    

This label will "fall down" and disappear.


Albeit a little late, here's my take on it:

https://github.com/pcperini/PCToastMessage


You could try my open source library TSMessages: https://github.com/toursprung/TSMessages

It's really easy to use and looks beautiful on iOS 5/6 and on iOS 7 as well.


I made my own. The class linked to by Krishnan was ugly and didn't rotate correctly.

https://github.com/esilverberg/ios-toast

Here's what it looks like:

Growl/toast style notifications library for iOS


I made it in this way :

+ (void)showToastMessage:(NSString *)message {    
UIAlertView *toast = [[UIAlertView alloc] initWithTitle:nil
                                                message:message
                                               delegate:nil
                                      cancelButtonTitle:nil
                                      otherButtonTitles:nil, nil];
[toast show];

// duration in seconds
int duration = 2;

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, duration * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
    [toast dismissWithClickedButtonIndex:0 animated:YES];
});

}

Updated solution for iOS9+:

+ (void)showToastMessage:(NSString *)message
                root:(id)view {
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil
                                                                             message:message
                                                                      preferredStyle:UIAlertControllerStyleAlert];
// duration in seconds
int duration = 2;
[view presentViewController:alertController animated:YES completion:nil];

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, duration * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
    [alertController dismissViewControllerAnimated:YES completion:nil];
});
}


MBProgressHUD?


Hey you are looking for this.

https://github.com/PaulSolt/WEPopover#readme


This one is exactly what you want. This one is quite handy as well since it has a completion block, please have a look :) https://github.com/PrajeetShrestha/EkToast


Swift 2.0:

The idea is to work out a Toast class with zero dependency on CocoaPods.

Reference: https://github.com/scalessec/Toast-Swift

Make an empty Swift file (file-New-File- Empty Swift File - Name it Toast.)

Add the following code to it.

//  Toast.swift

import UIKit
import ObjectiveC

enum ToastPosition {
 case Top
 case Center
 case Bottom
}

extension UIView {

 private struct ToastKeys {
  static var Timer        = "CSToastTimerKey"
  static var Duration     = "CSToastDurationKey"
  static var Position     = "CSToastPositionKey"
  static var Completion   = "CSToastCompletionKey"
  static var ActiveToast  = "CSToastActiveToastKey"
  static var ActivityView = "CSToastActivityViewKey"
  static var Queue        = "CSToastQueueKey"
 }

 private class ToastCompletionWrapper {
  var completion: ((Bool) -> Void)?

  init(_ completion: ((Bool) -> Void)?) {
   self.completion = completion
  }
 }

 private enum ToastError: ErrorType {
  case InsufficientData
 }

 private var queue: NSMutableArray {
  get {
   if let queue = objc_getAssociatedObject(self, &ToastKeys.Queue) as? NSMutableArray {
    return queue
   } else {
    let queue = NSMutableArray()
    objc_setAssociatedObject(self, &ToastKeys.Queue, queue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    return queue
   }
  }
 }

 // MARK: - Make Toast Methods
 func makeToast(message: String) {
  self.makeToast(message, duration: ToastManager.shared.duration, position: ToastManager.shared.position)
 }
 func makeToast(message: String, duration: NSTimeInterval, position: ToastPosition) {
  self.makeToast(message, duration: duration, position: position, style: nil)
 }
 func makeToast(message: String, duration: NSTimeInterval, position: CGPoint) {
  self.makeToast(message, duration: duration, position: position, style: nil)
 }
 func makeToast(message: String, duration: NSTimeInterval, position: ToastPosition, style: ToastStyle?) {
  self.makeToast(message, duration: duration, position: position, title: nil, image: nil, style: style, completion: nil)
 }
 func makeToast(message: String, duration: NSTimeInterval, position: CGPoint, style: ToastStyle?) {
  self.makeToast(message, duration: duration, position: position, title: nil, image: nil, style: style, completion: nil)
 }
 func makeToast(message: String?, duration: NSTimeInterval, position: ToastPosition, title: String?, image: UIImage?, style: ToastStyle?, completion: ((didTap: Bool) -> Void)?) {
  var toastStyle = ToastManager.shared.style
  if let style = style {
   toastStyle = style
  }

  do {
   let toast = try self.toastViewForMessage(message, title: title, image: image, style: toastStyle)
   self.showToast(toast, duration: duration, position: position, completion: completion)
  } catch ToastError.InsufficientData {
   print("Error: message, title, and image are all nil")
  } catch {}
 }

 func makeToast(message: String?, duration: NSTimeInterval, position: CGPoint, title: String?, image: UIImage?, style: ToastStyle?, completion: ((didTap: Bool) -> Void)?) {
  var toastStyle = ToastManager.shared.style
  if let style = style {
   toastStyle = style
  }

  do {
   let toast = try self.toastViewForMessage(message, title: title, image: image, style: toastStyle)
   self.showToast(toast, duration: duration, position: position, completion: completion)
  } catch ToastError.InsufficientData {
   print("Error: message, title, and image cannot all be nil")
  } catch {}
 }

 // MARK: - Show Toast Methods
 func showToast(toast: UIView) {
  self.showToast(toast, duration: ToastManager.shared.duration, position: ToastManager.shared.position, completion: nil)
 }
 func showToast(toast: UIView, duration: NSTimeInterval, position: ToastPosition, completion: ((didTap: Bool) -> Void)?) {
  let point = self.centerPointForPosition(position, toast: toast)
  self.showToast(toast, duration: duration, position: point, completion: completion)
 }

 func showToast(toast: UIView, duration: NSTimeInterval, position: CGPoint, completion: ((didTap: Bool) -> Void)?) {
  objc_setAssociatedObject(toast, &ToastKeys.Completion, ToastCompletionWrapper(completion), .OBJC_ASSOCIATION_RETAIN_NONATOMIC);

  if let _ = objc_getAssociatedObject(self, &ToastKeys.ActiveToast) as? UIView where ToastManager.shared.queueEnabled {
   objc_setAssociatedObject(toast, &ToastKeys.Duration, NSNumber(double: duration), .OBJC_ASSOCIATION_RETAIN_NONATOMIC);
   objc_setAssociatedObject(toast, &ToastKeys.Position, NSValue(CGPoint: position), .OBJC_ASSOCIATION_RETAIN_NONATOMIC);

   self.queue.addObject(toast)
  } else {
   self.showToast(toast, duration: duration, position: position)
  }
 }

 // MARK: - Activity Methods
 func makeToastActivity(position: ToastPosition) {
  // sanity
  if let _ = objc_getAssociatedObject(self, &ToastKeys.ActiveToast) as? UIView {
   return
  }

  let toast = self.createToastActivityView()
  let point = self.centerPointForPosition(position, toast: toast)
  self.makeToastActivity(toast, position: point)
 }
 func makeToastActivity(position: CGPoint) {
  // sanity
  if let _ = objc_getAssociatedObject(self, &ToastKeys.ActiveToast) as? UIView {
   return
  }

  let toast = self.createToastActivityView()
  self.makeToastActivity(toast, position: position)
 }

 //Dismisses the active toast activity indicator view.
 func hideToastActivity() {
  if let toast = objc_getAssociatedObject(self, &ToastKeys.ActivityView) as? UIView {
   UIView.animateWithDuration(ToastManager.shared.style.fadeDuration, delay: 0.0, options: [.CurveEaseIn, .BeginFromCurrentState], animations: { () -> Void in
    toast.alpha = 0.0
    }, completion: { (finished: Bool) -> Void in
     toast.removeFromSuperview()
     objc_setAssociatedObject(self, &ToastKeys.ActivityView, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
   })
  }
 }

 // MARK: - Private Activity Methods
 private func makeToastActivity(toast: UIView, position: CGPoint) {
  toast.alpha = 0.0
  toast.center = position

  objc_setAssociatedObject(self, &ToastKeys.ActivityView, toast, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)

  self.addSubview(toast)

  UIView.animateWithDuration(ToastManager.shared.style.fadeDuration, delay: 0.0, options: .CurveEaseOut, animations: { () -> Void in
   toast.alpha = 1.0
   }, completion: nil)
 }

 private func createToastActivityView() -> UIView {
  let style = ToastManager.shared.style

  let activityView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: style.activitySize.width, height: style.activitySize.height))
  activityView.backgroundColor = style.backgroundColor
  activityView.autoresizingMask = [.FlexibleLeftMargin, .FlexibleRightMargin, .FlexibleTopMargin, .FlexibleBottomMargin]
  activityView.layer.cornerRadius = style.cornerRadius

  if style.displayShadow {
   activityView.layer.shadowColor = style.shadowColor.CGColor
   activityView.layer.shadowOpacity = style.shadowOpacity
   activityView.layer.shadowRadius = style.shadowRadius
   activityView.layer.shadowOffset = style.shadowOffset
  }

  let activityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: .WhiteLarge)
  activityIndicatorView.center = CGPoint(x: activityView.bounds.size.width / 2.0, y: activityView.bounds.size.height / 2.0)
  activityView.addSubview(activityIndicatorView)
  activityIndicatorView.startAnimating()

  return activityView
 }

 // MARK: - Private Show/Hide Methods
 private func showToast(toast: UIView, duration: NSTimeInterval, position: CGPoint) {
  toast.center = position
  toast.alpha = 0.0

  if ToastManager.shared.tapToDismissEnabled {
   let recognizer = UITapGestureRecognizer(target: self, action: "handleToastTapped:")
   toast.addGestureRecognizer(recognizer)
   toast.userInteractionEnabled = true
   toast.exclusiveTouch = true
  }

  objc_setAssociatedObject(self, &ToastKeys.ActiveToast, toast, .OBJC_ASSOCIATION_RETAIN_NONATOMIC);

  self.addSubview(toast)

  UIView.animateWithDuration(ToastManager.shared.style.fadeDuration, delay: 0.0, options: [.CurveEaseOut, .AllowUserInteraction], animations: { () -> Void in
   toast.alpha = 1.0
   }) { (Bool finished) -> Void in
    let timer = NSTimer(timeInterval: duration, target: self, selector: "toastTimerDidFinish:", userInfo: toast, repeats: false)
    NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
    objc_setAssociatedObject(toast, &ToastKeys.Timer, timer, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
  }
 }

 private func hideToast(toast: UIView) {
  self.hideToast(toast, fromTap: false)
 }

 private func hideToast(toast: UIView, fromTap: Bool) {

  UIView.animateWithDuration(ToastManager.shared.style.fadeDuration, delay: 0.0, options: [.CurveEaseIn, .BeginFromCurrentState], animations: { () -> Void in
   toast.alpha = 0.0
   }) { (didFinish: Bool) -> Void in
    toast.removeFromSuperview()

    objc_setAssociatedObject(self, &ToastKeys.ActiveToast, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    if let wrapper = objc_getAssociatedObject(toast, &ToastKeys.Completion) as? ToastCompletionWrapper, completion = wrapper.completion {
     completion(fromTap)
    }

    if let nextToast = self.queue.firstObject as? UIView, duration = objc_getAssociatedObject(nextToast, &ToastKeys.Duration) as? NSNumber, position = objc_getAssociatedObject(nextToast, &ToastKeys.Position) as? NSValue {
     self.queue.removeObjectAtIndex(0)
     self.showToast(nextToast, duration: duration.doubleValue, position: position.CGPointValue())
    }
  }
 }

 // MARK: - Events
 func handleToastTapped(recognizer: UITapGestureRecognizer) {
  if let toast = recognizer.view, timer = objc_getAssociatedObject(toast, &ToastKeys.Timer) as? NSTimer {
   timer.invalidate()
   self.hideToast(toast, fromTap: true)
  }
 }

 func toastTimerDidFinish(timer: NSTimer) {
  if let toast = timer.userInfo as? UIView {
   self.hideToast(toast)
  }
 }

 // MARK: - Toast Construction
 func toastViewForMessage(message: String?, title: String?, image: UIImage?, style: ToastStyle) throws -> UIView {
  // sanity
  if message == nil && title == nil && image == nil {
   throw ToastError.InsufficientData
  }

  var messageLabel: UILabel?
  var titleLabel: UILabel?
  var imageView: UIImageView?

  let wrapperView = UIView()
  wrapperView.backgroundColor = style.backgroundColor
  wrapperView.autoresizingMask = [.FlexibleLeftMargin, .FlexibleRightMargin, .FlexibleTopMargin, .FlexibleBottomMargin]
  wrapperView.layer.cornerRadius = style.cornerRadius

  if style.displayShadow {
   wrapperView.layer.shadowColor = UIColor.blackColor().CGColor
   wrapperView.layer.shadowOpacity = style.shadowOpacity
   wrapperView.layer.shadowRadius = style.shadowRadius
   wrapperView.layer.shadowOffset = style.shadowOffset
  }

  if let image = image {
   imageView = UIImageView(image: image)
   imageView?.contentMode = .ScaleAspectFit
   imageView?.frame = CGRect(x: style.horizontalPadding, y: style.verticalPadding, width: style.imageSize.width, height: style.imageSize.height)
  }

  var imageRect = CGRectZero

  if let imageView = imageView {
   imageRect.origin.x = style.horizontalPadding
   imageRect.origin.y = style.verticalPadding
   imageRect.size.width = imageView.bounds.size.width
   imageRect.size.height = imageView.bounds.size.height
  }

  if let title = title {
   titleLabel = UILabel()
   titleLabel?.numberOfLines = style.titleNumberOfLines
   titleLabel?.font = style.titleFont
   titleLabel?.textAlignment = style.titleAlignment
   titleLabel?.lineBreakMode = .ByTruncatingTail
   titleLabel?.textColor = style.titleColor
   titleLabel?.backgroundColor = UIColor.clearColor();
   titleLabel?.text = title;

   let maxTitleSize = CGSize(width: (self.bounds.size.width * style.maxWidthPercentage) - imageRect.size.width, height: self.bounds.size.height * style.maxHeightPercentage)
   let titleSize = titleLabel?.sizeThatFits(maxTitleSize)
   if let titleSize = titleSize {
    titleLabel?.frame = CGRect(x: 0.0, y: 0.0, width: titleSize.width, height: titleSize.height)
   }
  }

  if let message = message {
   messageLabel = UILabel()
   messageLabel?.text = message
   messageLabel?.numberOfLines = style.messageNumberOfLines
   messageLabel?.font = style.messageFont
   messageLabel?.textAlignment = style.messageAlignment
   messageLabel?.lineBreakMode = .ByTruncatingTail;
   messageLabel?.textColor = style.messageColor
   messageLabel?.backgroundColor = UIColor.clearColor()

   let maxMessageSize = CGSize(width: (self.bounds.size.width * style.maxWidthPercentage) - imageRect.size.width, height: self.bounds.size.height * style.maxHeightPercentage)
   let messageSize = messageLabel?.sizeThatFits(maxMessageSize)
   if let messageSize = messageSize {
    messageLabel?.frame = CGRect(x: 0.0, y: 0.0, width: messageSize.width, height: messageSize.height)
   }
  }

  var titleRect = CGRectZero

  if let titleLabel = titleLabel {
   titleRect.origin.x = imageRect.origin.x + imageRect.size.width + style.horizontalPadding
   titleRect.origin.y = style.verticalPadding
   titleRect.size.width = titleLabel.bounds.size.width
   titleRect.size.height = titleLabel.bounds.size.height
  }

  var messageRect = CGRectZero

  if let messageLabel = messageLabel {
   messageRect.origin.x = imageRect.origin.x + imageRect.size.width + style.horizontalPadding
   messageRect.origin.y = titleRect.origin.y + titleRect.size.height + style.verticalPadding
   messageRect.size.width = messageLabel.bounds.size.width
   messageRect.size.height = messageLabel.bounds.size.height
  }

  let longerWidth = max(titleRect.size.width, messageRect.size.width)
  let longerX = max(titleRect.origin.x, messageRect.origin.x)
  let wrapperWidth = max((imageRect.size.width + (style.horizontalPadding * 2.0)), (longerX + longerWidth + style.horizontalPadding))
  let wrapperHeight = max((messageRect.origin.y + messageRect.size.height + style.verticalPadding), (imageRect.size.height + (style.verticalPadding * 2.0)))

  wrapperView.frame = CGRect(x: 0.0, y: 0.0, width: wrapperWidth, height: wrapperHeight)

  if let titleLabel = titleLabel {
   titleLabel.frame = titleRect
   wrapperView.addSubview(titleLabel)
  }

  if let messageLabel = messageLabel {
   messageLabel.frame = messageRect
   wrapperView.addSubview(messageLabel)
  }

  if let imageView = imageView {
   wrapperView.addSubview(imageView)
  }

  return wrapperView
 }

 // MARK: - Helpers
 private func centerPointForPosition(position: ToastPosition, toast: UIView) -> CGPoint {
  let padding: CGFloat = ToastManager.shared.style.verticalPadding

  switch(position) {
  case .Top:
   return CGPoint(x: self.bounds.size.width / 2.0, y: (toast.frame.size.height / 2.0) + padding)
  case .Center:
   return CGPoint(x: self.bounds.size.width / 2.0, y: self.bounds.size.height / 2.0)
  case .Bottom:
   return CGPoint(x: self.bounds.size.width / 2.0, y: (self.bounds.size.height - (toast.frame.size.height / 2.0)) - padding)
  }
 }
}

// MARK: - Toast Style
struct ToastStyle {

 var backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.8)
 var titleColor = UIColor.whiteColor()
 var messageColor = UIColor.whiteColor()
 var maxWidthPercentage: CGFloat = 0.8 {
  didSet {
   maxWidthPercentage = max(min(maxWidthPercentage, 1.0), 0.0)
  }
 }


 var maxHeightPercentage: CGFloat = 0.8 {
  didSet {
   maxHeightPercentage = max(min(maxHeightPercentage, 1.0), 0.0)
  }
 }

 var horizontalPadding: CGFloat = 10.0
 var verticalPadding: CGFloat = 10.0
 var cornerRadius: CGFloat = 10.0;
 var titleFont = UIFont.boldSystemFontOfSize(16.0)
 var messageFont = UIFont.systemFontOfSize(16.0)
 var titleAlignment = NSTextAlignment.Left
 var messageAlignment = NSTextAlignment.Left
 var titleNumberOfLines = 0;
 var messageNumberOfLines = 0;
 var displayShadow = false;
 var shadowColor = UIColor.blackColor()
 var shadowOpacity: Float = 0.8 {
  didSet {
   shadowOpacity = max(min(shadowOpacity, 1.0), 0.0)
  }
 }

 var shadowRadius: CGFloat = 6.0
 var shadowOffset = CGSize(width: 4.0, height: 4.0)
 var imageSize = CGSize(width: 80.0, height: 80.0)
 var activitySize = CGSize(width: 100.0, height: 100.0)
 var fadeDuration: NSTimeInterval = 0.2

}

// MARK: - Toast Manager
class ToastManager {

 static let shared = ToastManager()
 var style = ToastStyle()
 var tapToDismissEnabled = true
 var queueEnabled = true
 var duration: NSTimeInterval = 3.0
 var position = ToastPosition.Bottom

}

Using Toast.swift:

// basic usage
  self.view.makeToast("Sample Toast")

  // toast with a specific duration and position
  self.view.makeToast("Sample Toast", duration: 3.0, position: .Top)

  // toast with all possible options
  self.view.makeToast("Sample Toast", duration: 2.0, position: CGPoint(x: 110.0, y: 110.0), title: "Toast Title", image: UIImage(named: "ic_120x120.png"), style:nil) { (didTap: Bool) -> Void in
   if didTap {
    print("completion from tap")
   } else {
    print("completion without tap")
   }
  }

  //display toast with an activity spinner
   self.view.makeToastActivity(.Center)

  // display any view as toast
  let sampleView = UIView(frame: CGRectMake(100,100,200,200))
  sampleView.backgroundColor = UIColor(patternImage: UIImage(named: "ic_120x120")!)

  self.view.showToast(sampleView)
  self.view.showToast(sampleView, duration: 3.0, position: .Top, completion: nil)

You can download a sample project from https://github.com/alvinreuben/ToastSample


This is something, which was needed by you. Created with lot of Animation Options, screen positions, and duration. Even you can provide your own duration. Check that out below.

https://github.com/avistyles/AVIToast


Swift 3

Have been using Rannie/Toast-Swift for Swift 3 very happily for a while now and can recommend it for a very similar "Android-like" experience. Its very simple to implement without needing another pod and pretty customizable depending on your needs.

Easy Peasy

view.makeToastActivity()
view.hideToastActivity()
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜