有人可以推荐一个库来在iOS上实现咆哮或吐司式通知吗?例如,在用户保存个人资料后,我希望通知淡入,延迟3秒,报告“个人资料已保存”并淡出。现在我有一个UIAlertView,用一个“OK”按钮中断用户的工作流程,我觉得这样做太过分了。
Android Toast class是我在iOS上寻找的一个例子。
谢谢!
答案 0 :(得分:45)
我创建了一个我认为你会觉得有用的解决方案: https://github.com/scalessec/toast
它被写为obj-c类,实质上是将makeToast方法添加到任何UIView实例中。例如:
[self.view makeToast:@"Profile saved"
duration:2.0
position:@"bottom"];
答案 1 :(得分:6)
我这样解决了:
在viewWillAppear中添加以下行以隐藏开头标签:
[toastLabel setHidden:TRUE];
在按钮点击(或其他一些事件)上添加以下代码:
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;
}];
此标签将“掉落”并消失。
答案 2 :(得分:5)
虽然有点晚了,但这是我的看法:
答案 3 :(得分:5)
您可以尝试我的开源库TSMessages:https://github.com/toursprung/TSMessages
它非常易于使用,在iOS 5/6和iOS 7上也很漂亮。
答案 4 :(得分:4)
答案 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)
嘿,你正在寻找这个。
答案 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)
下载示例项目
答案 10 :(得分:0)
这是你需要的东西。创建了许多动画选项,屏幕位置和持续时间。即使你可以提供自己的持续时间。检查下面的内容。
答案 11 :(得分:0)
Swift 3
现在已经非常愉快地使用Rannie/Toast-Swift对Swift 3进行了一段时间,并且可以推荐它用于非常类似的“类似Android”的体验。它的实现非常简单,无需另外的pod,可根据您的需求进行定制。
Easy Peasy
view.makeToastActivity()
view.hideToastActivity()