如何在Swift中创建一个toast消息?

106

有没有办法在Swift中制作一个Toast消息?

我已经尝试过Objective C,但是在Swift中找不到解决方案。

[self.view makeToast:@"Account created Successfully"
                     duration:0.5
                     position:@"bottom"];

1
https://github.com/Rannie/Toast-Swift - Kirit Modi
对于正在寻找此内容的任何人,我首先会问一句话:你真的需要吗?我会说,与iOS相比,Android上的toast更为常见。就个人而言,作为长期使用iOS的用户,我认为toast更令人讨厌而不是有用的,如果操作已成功完成,则无需toast提示。 - Leszek Szary
26个回答

242
extension UIViewController {

func showToast(message : String, font: UIFont) {

    let toastLabel = UILabel(frame: CGRect(x: self.view.frame.size.width/2 - 75, y: self.view.frame.size.height-100, width: 150, height: 35))
    toastLabel.backgroundColor = UIColor.black.withAlphaComponent(0.6)
    toastLabel.textColor = UIColor.white
    toastLabel.font = font
    toastLabel.textAlignment = .center;
    toastLabel.text = message
    toastLabel.alpha = 1.0
    toastLabel.layer.cornerRadius = 10;
    toastLabel.clipsToBounds  =  true
    self.view.addSubview(toastLabel)
    UIView.animate(withDuration: 4.0, delay: 0.1, options: .curveEaseOut, animations: {
         toastLabel.alpha = 0.0
    }, completion: {(isCompleted) in
        toastLabel.removeFromSuperview()
    })
} }

使用方法如下:

self.showToast(message: "Your Toast Message", font: .systemFont(ofSize: 12.0))

4
完美的回答。您可以添加“toastLabel.font = UIFont(name:“IranSansMobile”,size:19)”来更改提示消息的字体。 - Milad Faridnia
1
当动画结束时,标签应该从视图中移除。 - Sulthan
1
嗨,@Mr.Bean,您能否动态自定义标签高度? - Ram
3
如果你想展示多行完整的消息,可以使用类似以下这样的代码:let toastLabel = UILabel(frame: CGRect(x: 0, y: self.view.frame.size.height-100, width: (self.view.frame.width - 10), height: 35)) toastLabel.numberOfLines = 0你可以根据需要更改高度。 - Udaya Sri
2
答案几乎完美,因为如果您在全屏模式下显示了某些内容,则需要通过UIApplication.shared.keyWindow?.addSubview(toastLabel)更改addsubview。 - Jean Raymond Daher
显示剩余13条评论

71

针对Swift 4版本

这是我使用布局约束的Toast版本,它的优势在于适用于任何文本大小(基于Tony Franzis的响应):

只需调用:Toast.show(message: "My message", myViewControllerName)

class Toast {
    static func show(message: String, controller: UIViewController) {
        let toastContainer = UIView(frame: CGRect())
        toastContainer.backgroundColor = UIColor.black.withAlphaComponent(0.6)
        toastContainer.alpha = 0.0
        toastContainer.layer.cornerRadius = 25;
        toastContainer.clipsToBounds  =  true

        let toastLabel = UILabel(frame: CGRect())
        toastLabel.textColor = UIColor.white
        toastLabel.textAlignment = .center;
        toastLabel.font.withSize(12.0)
        toastLabel.text = message
        toastLabel.clipsToBounds  =  true
        toastLabel.numberOfLines = 0

        toastContainer.addSubview(toastLabel)
        controller.view.addSubview(toastContainer)

        toastLabel.translatesAutoresizingMaskIntoConstraints = false
        toastContainer.translatesAutoresizingMaskIntoConstraints = false

        let a1 = NSLayoutConstraint(item: toastLabel, attribute: .leading, relatedBy: .equal, toItem: toastContainer, attribute: .leading, multiplier: 1, constant: 15)
        let a2 = NSLayoutConstraint(item: toastLabel, attribute: .trailing, relatedBy: .equal, toItem: toastContainer, attribute: .trailing, multiplier: 1, constant: -15)
        let a3 = NSLayoutConstraint(item: toastLabel, attribute: .bottom, relatedBy: .equal, toItem: toastContainer, attribute: .bottom, multiplier: 1, constant: -15)
        let a4 = NSLayoutConstraint(item: toastLabel, attribute: .top, relatedBy: .equal, toItem: toastContainer, attribute: .top, multiplier: 1, constant: 15)
        toastContainer.addConstraints([a1, a2, a3, a4])

        let c1 = NSLayoutConstraint(item: toastContainer, attribute: .leading, relatedBy: .equal, toItem: controller.view, attribute: .leading, multiplier: 1, constant: 65)
        let c2 = NSLayoutConstraint(item: toastContainer, attribute: .trailing, relatedBy: .equal, toItem: controller.view, attribute: .trailing, multiplier: 1, constant: -65)
        let c3 = NSLayoutConstraint(item: toastContainer, attribute: .bottom, relatedBy: .equal, toItem: controller.view, attribute: .bottom, multiplier: 1, constant: -75)
        controller.view.addConstraints([c1, c2, c3])

        UIView.animate(withDuration: 0.5, delay: 0.0, options: .curveEaseIn, animations: {
            toastContainer.alpha = 1.0
        }, completion: { _ in
            UIView.animate(withDuration: 0.5, delay: 1.5, options: .curveEaseOut, animations: {
                toastContainer.alpha = 0.0
            }, completion: {_ in
                toastContainer.removeFromSuperview()
            })
        })
    }
}

1
@Samo 很好,谢谢。只是在使用 iPhone X 时,toastContainer 部分被 UITabBar 遮挡了。在 c3 中,我将 -75 改为 -100 来修复它。 - Mario Huizinga
1
这很棒,使你能够在没有第三方的情况下完成它。我在我的应用程序中随处重复使用它。感谢这个答案。 - Onur Tuna
1
太棒了!但我已经更改了toastContainer的一些限制条件,使其具有动态宽度,并将其作为UIViewController的扩展。 这是: let c1 = NSLayoutConstraint(item: toastContainer, attribute: .width, relatedBy: .lessThanOrEqual, toItem: view, attribute: .width, multiplier: 1, constant: -90) let c2 = NSLayoutConstraint(item: toastContainer, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 1, constant: 1) - sVd
1
为了帮助其他人,将该类放入自己的Swift文件中。然后在您的调用ViewController中,像这样调用它Toast.show(message: "My message", controller: self)我还必须将c3约束更改为-150。 - app4g
@Samo 不错的解决方案...我将底部布局约束改为使用安全区域,这可能对其他人有帮助,toItem: controller.view.safeAreaLayoutGuide 然后使用常量属性将其与选项卡或导航栏进一步分开。谢谢!! - Brian Barton

16

有一个第三方库可支持通过一行代码定制化的 toast 通知。这里是一个简单的示例:

import Toast_Swift

...

// basic usage
self.view.makeToast("This is a piece of toast")

// toast with a specific duration and position
self.view.makeToast("This is a piece of toast", duration: 3.0, position: .top)

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

(已更新至Swift 3/4+)


16

只需添加以下方法。这将使用动画显示不同颜色的消息(消息从左到右出现并消失)。

Swift 3.0 -

class Toast
{
    class private func showAlert(backgroundColor:UIColor, textColor:UIColor, message:String)
    {
        
        let appDelegate: AppDelegate = UIApplication.shared.delegate as! AppDelegate
        let label = UILabel(frame: CGRect.zero)
        label.textAlignment = NSTextAlignment.center
        label.text = message
        label.font = UIFont(name: "", size: 15)
        label.adjustsFontSizeToFitWidth = true
        
        label.backgroundColor =  backgroundColor //UIColor.whiteColor()
        label.textColor = textColor //TEXT COLOR
        
        label.sizeToFit()
        label.numberOfLines = 4
        label.layer.shadowColor = UIColor.gray.cgColor
        label.layer.shadowOffset = CGSize(width: 4, height: 3)
        label.layer.shadowOpacity = 0.3
        label.frame = CGRect(x: appDelegate.window!.frame.size.width, y: 64, width: appDelegate.window!.frame.size.width, height: 44)
        
        label.alpha = 1
        
        appDelegate.window!.addSubview(label)
        
        var basketTopFrame: CGRect = label.frame;
        basketTopFrame.origin.x = 0;
        
        UIView.animate(withDuration
            :2.0, delay: 0.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.1, options: UIViewAnimationOptions.curveEaseOut, animations: { () -> Void in
                label.frame = basketTopFrame
        },  completion: {
            (value: Bool) in
            UIView.animate(withDuration:2.0, delay: 2.0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.1, options: UIViewAnimationOptions.curveEaseIn, animations: { () -> Void in
                label.alpha = 0
            },  completion: {
                (value: Bool) in
                label.removeFromSuperview()
            })
        })
    }
    
    class func showPositiveMessage(message:String)
    {
        showAlert(backgroundColor: UIColor.green, textColor: UIColor.white, message: message)
    }
    class func showNegativeMessage(message:String)
    {
        showAlert(backgroundColor: UIColor.red, textColor: UIColor.white, message: message)
    }
}

16

我在Swift 5中还有两个解决方案:

最佳解决方案(据我所见)

优点:

  1. 在旋转屏幕时能正常工作。
  2. 使用约束进行定位。
  3. 与SafeArea一起可以正常工作。

缺点:

  1. 需要扩展 UILabel 类来添加缩进。通过将 UILabel 放入 UIVIew 中可以避免这个问题。

代码:

class ToastLabel: UILabel {
    var textInsets = UIEdgeInsets.zero {
        didSet { invalidateIntrinsicContentSize() }
    }

    override func textRect(forBounds bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect {
        let insetRect = bounds.inset(by: textInsets)
        let textRect = super.textRect(forBounds: insetRect, limitedToNumberOfLines: numberOfLines)
        let invertedInsets = UIEdgeInsets(top: -textInsets.top, left: -textInsets.left, bottom: -textInsets.bottom, right: -textInsets.right)

        return textRect.inset(by: invertedInsets)
    }

    override func drawText(in rect: CGRect) {
        super.drawText(in: rect.inset(by: textInsets))
    }
}

extension UIViewController {
    static let DELAY_SHORT = 1.5
    static let DELAY_LONG = 3.0

    func showToast(_ text: String, delay: TimeInterval = DELAY_LONG) {
        let label = ToastLabel()
        label.backgroundColor = UIColor(white: 0, alpha: 0.5)
        label.textColor = .white
        label.textAlignment = .center
        label.font = UIFont.systemFont(ofSize: 15)
        label.alpha = 0
        label.text = text
        label.clipsToBounds = true
        label.layer.cornerRadius = 20
        label.numberOfLines = 0
        label.textInsets = UIEdgeInsets(top: 10, left: 15, bottom: 10, right: 15)
        label.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(label)

        let saveArea = view.safeAreaLayoutGuide
        label.centerXAnchor.constraint(equalTo: saveArea.centerXAnchor, constant: 0).isActive = true
        label.leadingAnchor.constraint(greaterThanOrEqualTo: saveArea.leadingAnchor, constant: 15).isActive = true
        label.trailingAnchor.constraint(lessThanOrEqualTo: saveArea.trailingAnchor, constant: -15).isActive = true
        label.bottomAnchor.constraint(equalTo: saveArea.bottomAnchor, constant: -30).isActive = true

        UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseIn, animations: {
            label.alpha = 1
        }, completion: { _ in
            UIView.animate(withDuration: 0.5, delay: delay, options: .curveEaseOut, animations: {
                label.alpha = 0
            }, completion: {_ in
                label.removeFromSuperview()
            })
        })
    }
}

使用方法:

class MyController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        showToast("Message")
    }
}

其他解决方案

优点:

  1. 在这个版本中,我没有使用与 UIViewController 的绑定。

缺点:

  1. 屏幕旋转后,标签不会移动。
  2. 无法正确处理多行字符串。

代码:

class Helper {
    static let DELAY_SHORT = 1.5
    static let DELAY_LONG = 3.0

    static func showToast(_ text: String, delay: TimeInterval = DELAY_LONG) {
        guard let window = UIApplication.shared.keyWindow else {
            return
        }

        let label = BaseLabel()
        label.backgroundColor = UIColor(white: 0, alpha: 0.5)
        label.textColor = .white
        label.textAlignment = .center
        label.font = UIFont.systemFont(ofSize: 15)
        label.alpha = 0
        label.text = text
        label.numberOfLines = 0

        var vertical: CGFloat = 0
        var size = label.intrinsicContentSize
        var width = min(size.width, window.frame.width - 60)
        if width != size.width {
            vertical = 10
            label.textAlignment = .justified
        }
        label.textInsets = UIEdgeInsets(top: vertical, left: 15, bottom: vertical, right: 15)

        size = label.intrinsicContentSize
        width = min(size.width, window.frame.width - 60)

        label.frame = CGRect(x: 20, y: window.frame.height - 90, width: width, height: size.height + 20)
        label.center.x = window.center.x
        label.layer.cornerRadius = min(label.frame.height/2, 25)
        label.layer.masksToBounds = true
        window.addSubview(label)

        UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseIn, animations: {
            label.alpha = 1
        }, completion: { _ in
            UIView.animate(withDuration: 0.5, delay: delay, options: .curveEaseOut, animations: {
                label.alpha = 0
            }, completion: {_ in
                label.removeFromSuperview()
            })
        })
    }
}

如何使用:

Helper.showToast("Message")

你还可以添加label.heightAnchor.constraint(equalToConstant: 35).active = true来设置视图高度,以使标签在文本上方和下方留出间距。 - siralexsir88
你的“其他解决方案”对我来说无法编译,TimeInterval不存在等。 - Martin Braun

12

如果你想要一个简单的提示框实现,请查看以下代码。

extension UIViewController{

func showToast(message : String, seconds: Double){
        let alert = UIAlertController(title: nil, message: message, preferredStyle: .alert)
        alert.view.backgroundColor = .black
        alert.view.alpha = 0.5
        alert.view.layer.cornerRadius = 15
        self.present(alert, animated: true)
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + seconds) {
            alert.dismiss(animated: true)
        }
    }
 }

从UIViewController中调用它

  self.showToast(message: "Updating...", seconds: 1.0)

5
这不是一个提示通知,而是一个模态警告。 - JaredH
完美的代码,运行得像魔法一样 :) - Najib.Nj

11

您需要的是 https://github.com/Rannie/Toast-Swift/blob/master/SwiftToastDemo/Toast/HRToast%2BUIView.swift

下载 HRToast + UIView.swift 类,将其拖放到项目中。确保在对话框上选中“如果需要复制项目”。

  //Usage:
  self.view.makeToast(message: "Simple Toast")
  self.view.makeToast(message: "Simple Toast", duration: 2.0, position:HRToastPositionTop)

  self.view.makeToast(message: "Simple Toast", duration: 2.0, position: HRToastPositionCenter, image: UIImage(named: "ic_120x120")!)

  self.view.makeToast(message: "It is just awesome", duration: 2.0, position: HRToastPositionDefault, title: "Simple Toast")

  self.view.makeToast(message: "It is just awesome", duration: 2.0, position: HRToastPositionCenter, title: "Simple Toast", image: UIImage(named: "ic_120x120")!)

  self.view.makeToastActivity()
  self.view.makeToastActivity(position: HRToastPositionCenter)
  self.view.makeToastActivity(position: HRToastPositionDefault, message: "Loading")
  self.view.makeToastActivityWithMessage(message: "Loading")

我该如何更改 Toast 的颜色?当我尝试使用 self.view.hr_setToastThemeColor(color: #ThemeColor) 时,它会出现错误。 - Dory

9
我一直在需要类似于Android的toast消息时使用这个扩展程序。只需将扩展程序复制到您的项目中,然后在您的UIViewController类中像下面这样调用函数即可:
self.toastMessage("Downloading...") 
// Extention is below

extension UIViewController {
  func toastMessage(_ message: String){
    guard let window = UIApplication.shared.keyWindow else {return}
    let messageLbl = UILabel()
    messageLbl.text = message
    messageLbl.textAlignment = .center
    messageLbl.font = UIFont.systemFont(ofSize: 12)
    messageLbl.textColor = .white
    messageLbl.backgroundColor = UIColor(white: 0, alpha: 0.5)

    let textSize:CGSize = messageLbl.intrinsicContentSize
    let labelWidth = min(textSize.width, window.frame.width - 40)

    messageLbl.frame = CGRect(x: 20, y: window.frame.height - 90, width: labelWidth + 30, height: textSize.height + 20)
    messageLbl.center.x = window.center.x
    messageLbl.layer.cornerRadius = messageLbl.frame.height/2
    messageLbl.layer.masksToBounds = true
    window.addSubview(messageLbl)

    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {

    UIView.animate(withDuration: 1, animations: {
        messageLbl.alpha = 0
    }) { (_) in
        messageLbl.removeFromSuperview()
    }
    }
}}

不要仅仅呈现代码!添加一些描述性文本来说明这段代码如何最好地回答问题,将提高答案的长期价值,并有助于在审核过程中防止其被删除。 - NightOwl888
1
代码本身已经很清楚了,但我还是进行了编辑。如果有做错的地方,请告诉我。 - Gopal krishan
宽度没有硬编码,非常好,谢谢! - Chandler

6
如果只需要一个简单的Toast消息,而不需要自定义字体、对齐方式、文本颜色等,则以下内容就可以了。
let messageVC = UIAlertController(title: "Message Title", message: "Account Created successfully" , preferredStyle: .actionSheet)
present(messageVC, animated: true) {
                Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false, block: { (_) in
                    messageVC.dismiss(animated: true, completion: nil)})}

.actionSheet 会从屏幕底部弹出警告,并由计时器控制显示持续时间。您可以将其作为扩展添加到UIViewController中,然后从任何地方调用它。


4
如果在 Objective-C 中定义了 makeToast:duration:position: 并进行了调用,那么 Swift 代码将为:
self.view.makeToast("Acount created Successfully", duration: 0.5, position: "bottom")

你可能需要使用一个桥接头文件才能在Swift代码中访问这些方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接