适用于iOS的Growl / toast风格通知库

时间:2011-05-17 04:32:31

标签: iphone ios notifications toast

有人可以推荐一个库来在iOS上实现咆哮或吐司式通知吗?例如,在用户保存个人资料后,我希望通知淡入,延迟3秒,报告“个人资料已保存”并淡出。现在我有一个UIAlertView,用一个“OK”按钮中断用户的工作流程,我觉得这样做太过分了。

Android Toast class是我在iOS上寻找的一个例子。

谢谢!

12 个答案:

答案 0 :(得分:45)

我创建了一个我认为你会觉得有用的解决方案: https://github.com/scalessec/toast

它被写为obj-c类,实质上是将makeToast方法添加到任何UIView实例中。例如:

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

答案 1 :(得分:6)

我这样解决了:

  1. 在视图上创建通用标签。将它全部放在屏幕上,给它你需要的尺寸和中心文字。
  2. 将其位置设置在“顶部” - 此标签必须位于控件列表中的所有控件下方。
  3. 将它添加到界面,属性,合成(让我们称之为“toastLabel”)。
  4. 将您的XIB文件与“toastLabel”相关联
  5. 在viewWillAppear中添加以下行以隐藏开头标签:

    [toastLabel setHidden:TRUE];
    
  6. 在按钮点击(或其他一些事件)上添加以下代码:

    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;
    }];
    
  7. 此标签将“掉落”并消失。

答案 2 :(得分:5)

虽然有点晚了,但这是我的看法:

https://github.com/pcperini/PCToastMessage

答案 3 :(得分:5)

您可以尝试我的开源库TSMessages:https://github.com/toursprung/TSMessages

它非常易于使用,在iOS 5/6和iOS 7上也很漂亮。

答案 4 :(得分:4)

我自己做了。由克里希南联系的班级很丑,没有正确旋转。

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

这是它的样子: enter image description here

答案 5 :(得分:3)

我这样做了:

+ (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];
});

}

更新了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];
});
}

答案 6 :(得分:2)

答案 7 :(得分:1)

嘿,你正在寻找这个。

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

答案 8 :(得分:1)

这个正是你想要的。 这个也非常方便,因为它有一个完成块,请看看:) https://github.com/PrajeetShrestha/EkToast

答案 9 :(得分:0)

Swift 2.0:

我们的想法是找出一个对CocoaPods零依赖的Toast类。

参考: https://github.com/scalessec/Toast-Swift

制作一个空的Swift文件(file-New-File-Empty Swift文件 - 将其命名为Toast。)

将以下代码添加到其中。

//  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

}

使用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)

您可以从https://github.com/alvinreuben/ToastSample

下载示例项目

答案 10 :(得分:0)

这是你需要的东西。创建了许多动画选项,屏幕位置和持续时间。即使你可以提供自己的持续时间。检查下面的内容。

https://github.com/avistyles/AVIToast

答案 11 :(得分:0)

Swift 3

现在已经非常愉快地使用Rannie/Toast-Swift对Swift 3进行了一段时间,并且可以推荐它用于非常类似的“类似Android”的体验。它的实现非常简单,无需另外的pod,可根据您的需求进行定制。

Easy Peasy

view.makeToastActivity()
view.hideToastActivity()