105

Is there any way to make a toast message in swift ?

I have tried in objective c but could not find solution in swift.

[self.view makeToast:@"Account created Successfully"
                     duration:0.5
                     position:@"bottom"];
Alex Zavatone
  • 4,106
  • 36
  • 54
Aabid Khan
  • 1,197
  • 2
  • 9
  • 7
  • 1
    https://github.com/Rannie/Toast-Swift – Kirit Modi Jul 21 '15 at 13:30
  • To anyone who is searching for this I would first ask do you really need this? I would say that toasts are more common on Android than on iOS. Personally as a long time iOS user I think toasts are more annoying than useful and if an action was completed successfully then you do not need a toast for that. – Leszek Szary Oct 13 '22 at 14:15

26 Answers26

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()
    })
} }

Use like this:

self.showToast(message: "Your Toast Message", font: .systemFont(ofSize: 12.0))
Mr. Bean
  • 4,221
  • 1
  • 19
  • 34
  • 4
    Perfect answer. You can add 'toastLabel.font = UIFont(name: "IranSansMobile", size: 19)' in order to change font of toast message. – Milad Faridnia Feb 21 '17 at 13:21
  • 1
    The label should be removed from the view when the animation ends. – Sulthan Mar 22 '17 at 09:08
  • 1
    @Sulthan you are welcome for an edit or upgrade of the answer, else i will update the answer soon !! – Mr. Bean Mar 22 '17 at 09:53
  • 1
    Hi @Mr.Bean can you please customise the label height dynamically. – Ram Feb 21 '18 at 04:47
  • Sure @kishan, i will do that very soon, both dynamic height and width. – Mr. Bean Feb 21 '18 at 06:03
  • Sorry @Ram, i was quite busy with my work, i will surely update the answer soon buddy – Mr. Bean Mar 20 '18 at 18:38
  • it works only for two words if you have more it won't be displayed – user924 Mar 30 '18 at 13:52
  • Please modify the code as per your needs, you cn pass width param in the function. – Mr. Bean Apr 14 '18 at 11:53
  • 3
    If you want to show the full message with multiline ,you can use something like this : 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 you can change the height accordingly. – Udaya Sri Dec 17 '18 at 12:30
  • Still works with Swift 5, thanks! @UdayaSri With your version the toast touches the left side of the screen but not the right. Imo Mr. Bean's version (calculating the x position) is better because the view will always be centered. – Neph Apr 25 '19 at 13:00
  • `toastLabel.font = UIFont(name: "Montserrat-Light", size: 12.0)` doesn't do anything in Swift 5 (at least in the emulator - can't test with a real device). Instead I'm using: `toastLabel.font = toastLabel.font.withSize(12)` – Neph Apr 25 '19 at 13:26
  • @Neph the line of code you mentioned is just used to supply font of your choice to the label. "Montserrat-Light" is a custom downloaded font, try using any other default font present in the system. – Mr. Bean Apr 26 '19 at 10:20
  • @Mr.Bean I tested it more for the font size and even with "Standard" (if I remember correctly, that's what's supposedly the default font), it never changed the size. – Neph Apr 29 '19 at 12:01
  • Sorry, "System", not "Standard". – Neph Apr 29 '19 at 14:36
  • @Mr.Bean I have a problem on swift 3 . Color text is not full visible. Can you help how to fix it ? – Ibrahim Sušić Oct 16 '19 at 13:11
  • 2
    the answer is almost perfect because it does not work if you have something displayed in fullscreen, you need to change the addsubview by UIApplication.shared.keyWindow?.addSubview(toastLabel) – Jean Raymond Daher Dec 19 '19 at 19:36
  • add toastLabel.numberOfLines = 0 so it can go more than 1 line – Jean Raymond Daher Dec 29 '20 at 20:59
  • If your toast does not work, make sure to wrap it inside DispatchQueue – Nedim Karavdic Mar 11 '21 at 22:18
71

For Swift 4

My version of a Toast that uses layout constraints, with the advantage that it works for any text size, as is (based in the response of Tony Franzis):

Just call: 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()
            })
        })
    }
}
user2167877
  • 1,676
  • 1
  • 14
  • 15
Samo
  • 2,093
  • 1
  • 17
  • 18
  • 1
    @Samo nice, thanks. Just the toastContainer is partly hidden behind the UITabBar when using an iPhone X. In c3 I changed -75 to -100 to fix it. – Mario Huizinga Jun 28 '18 at 08:37
  • 1
    It's wonderful and allows you to do it without a 3rd party. I reuse it everywhere in my app. Thanks for this answer. – Onur Tuna Jan 30 '19 at 07:23
  • 1
    Wonderful! But I've changed a bit constraints for toastContainer to have dynamic width and make it as an extension of UIViewController. Here is: 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 Jul 24 '19 at 14:30
  • 1
    to help anyone else out, Put the class into it's own swift file. then in your calling ViewController, call it like so ```Toast.show(message: "My message", controller: self)``` I also had to change the c3 constraint to -150 – app4g Mar 28 '21 at 12:25
  • @Samo nice solution... I changed the bottom layout constraint to use the safe area instead...might be helpful to others, `toItem: controller.view.safeAreaLayoutGuide` then use the constant attribute to space it further away from tab or navigation bar. Thanks!! – Brian Barton Apr 24 '23 at 21:55
16

Just add the method below. This will show message in different colors with animation (message appearing from left to right & disappear).

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)
    }
}
Alex Zavatone
  • 4,106
  • 36
  • 54
Yogesh Lolusare
  • 2,162
  • 1
  • 24
  • 35
16

There is a 3rd party library that supports customizable toast notification with single line of code. Here is a simple example of it:

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

(Updated for Swift 3/4+)

mathema
  • 939
  • 11
  • 22
16

I have two more solutions on Swift 5:

Best solution (in my opinion)

Advantage:

  1. Works correctly when rotating the screen.
  2. Constraints are used for positioning.
  3. Works correctly with SafeArea.

Disadvantages:

  1. It is required to extend the UILabel class to add indents. One could do without this by placing the UILabel in the UIVIew.

Code:

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()
            })
        })
    }
}

How to use:

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

        showToast("Message")
    }
}

Other solution

Advantage:

  1. In this version, I don't use a binding to UIViewController

Disadvantages:

  1. After screen rotation, the label does not move.
  2. Does not work correctly with multi-line strings.

Code:

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()
            })
        })
    }
}

How to use:

Helper.showToast("Message")
Vergiliy
  • 1,248
  • 1
  • 13
  • 22
  • You can also add `label.heightAnchor.constraint(equalToConstant: 35).active = true` to set the view height so the label will leave spacing above and below the text. – siralexsir88 Jun 01 '20 at 18:06
  • Your "other solution" won't compile for me, `TimeInterval` does not exist, etc. – Martin Braun May 03 '21 at 05:13
12

Swift 5. If you want a simple toast implementation, please find the code below.

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)
        }
    }
 }

Call It from UIViewController

  self.showToast(message: "Updating...", seconds: 1.0)
JaredH
  • 2,338
  • 1
  • 30
  • 40
Sonu Rajput
  • 145
  • 1
  • 2
11

What exactly you need is https://github.com/Rannie/Toast-Swift/blob/master/SwiftToastDemo/Toast/HRToast%2BUIView.swift .

Download the HRToast + UIView.swift class and drag and drop to project. Make sure you check 'copy items if needed' on dialogue box.

  //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")
Alvin George
  • 14,148
  • 92
  • 64
  • how can i change the color of toast.. when i m trying with `self.view.hr_setToastThemeColor(color: #ThemeColor)` it is giving error – Dory Jun 09 '16 at 12:28
9

I have been using this extension when ever i need toast-message like android.. Just copy the extension to you project and then in your UIViewController class, call the function like

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()
    }
    }
}}
Gopal krishan
  • 179
  • 2
  • 4
  • Don't just blurt out code! Adding some text to describe how this code best answers the question will improve the long-term value of the answer and help to prevent it from being deleted during review. – NightOwl888 Apr 17 '18 at 05:50
  • 1
    Code was self explanatory sir but still i edited. If not done correctly please do tell. – Gopal krishan Apr 19 '18 at 06:39
  • The width is not hardcoded, excellent, thanks! – Chandler Mar 22 '21 at 01:18
6

If the need is for a simple Toast message without fancy customization of font, alignment, text color, etc. then the following would do just fine

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 presents the alert from Bottom of the screen and the Timer takes care of the display duration. You can add this as an extension to UIViewController and then call it from anywhere.

Abhishek
  • 1,261
  • 1
  • 13
  • 30
4

if makeToast:duration:position: is defined in objective-c and can be called, then the swift code will be

self.view.makeToast("Acount created Successfully", duration: 0.5, position: "bottom")

You may need to use a bridging header to gain access to those method in your swift code though.

tomahh
  • 13,441
  • 3
  • 49
  • 70
3

@mr-bean code updated to latest Swift version (3.x)

    let toastLabel =
        UILabel(frame:
            CGRect(x: self.view.frame.size.width/2 - 150,
                   y: self.view.frame.size.height-100,
                   width: 300,
                   height: 35))
    toastLabel.backgroundColor = UIColor.black
    toastLabel.textColor = UIColor.white
    toastLabel.textAlignment = NSTextAlignment.center
    self.view.addSubview(toastLabel)
    toastLabel.text = message
    toastLabel.alpha = 1.0
    toastLabel.layer.cornerRadius = 10;
    toastLabel.clipsToBounds  =  true
    UIView.animate(withDuration: 4.0, animations: {
        toastLabel.alpha = 0.0
    })
romualdr
  • 184
  • 7
3

I have modified the @samo's answer with:

  • Proper variables name's

  • Leading and Trailing constraints changed to center constraint.

Now message will adjust its width according to message and it will be centred.

extension UIViewController {
        func showToast(message: String) {
            let toastContainer = UIView(frame: CGRect())
            toastContainer.backgroundColor = UIColor.black.withAlphaComponent(0.6)
            toastContainer.alpha = 0.0
            toastContainer.layer.cornerRadius = 20;
            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)
            self.view.addSubview(toastContainer)

            toastLabel.translatesAutoresizingMaskIntoConstraints = false
            toastContainer.translatesAutoresizingMaskIntoConstraints = false

            let centerX = NSLayoutConstraint(item: toastLabel, attribute: .centerX, relatedBy: .equal, toItem: toastContainer, attribute: .centerXWithinMargins, multiplier: 1, constant: 0)
            let lableBottom = NSLayoutConstraint(item: toastLabel, attribute: .bottom, relatedBy: .equal, toItem: toastContainer, attribute: .bottom, multiplier: 1, constant: -15)
            let lableTop = NSLayoutConstraint(item: toastLabel, attribute: .top, relatedBy: .equal, toItem: toastContainer, attribute: .top, multiplier: 1, constant: 15)
            toastContainer.addConstraints([centerX, lableBottom, lableTop])

            let containerCenterX = NSLayoutConstraint(item: toastContainer, attribute: .centerX, relatedBy: .equal, toItem: self.view, attribute: .centerX, multiplier: 1, constant: 0)
            let containerTrailing = NSLayoutConstraint(item: toastContainer, attribute: .width, relatedBy: .equal, toItem: toastLabel, attribute: .width, multiplier: 1.1, constant: 0)
            let containerBottom = NSLayoutConstraint(item: toastContainer, attribute: .bottom, relatedBy: .equal, toItem: self.view, attribute: .bottom, multiplier: 1, constant: -75)
            self.view.addConstraints([containerCenterX,containerTrailing, containerBottom])

            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()
                })
            })
        }
    }
A R
  • 461
  • 4
  • 14
2

I know there are accepted answers but they all seem to have a big flaw - if you show several toasts in a short period of time they will show on top of each other. Here is my implementation which takes this problem into consideration:

class Toast: UILabel {

private let BOTTOM_MARGIN: CGFloat = 16
private let SIDE_MARGIN: CGFloat = 16
private let HEIGHT: CGFloat = 35
private let SHOW_TIME_SECONDS = TimeInterval(3)
private let BACKGROUND_COLOR = UIColor.darkGray.withAlphaComponent(0.7).cgColor
private let TEXT_COLOR = UIColor.white
private let ANIMATION_DURATION_SEC = 0.33

private static var queue: [ToastHolder] = []
private static var showing: Toast?

init(_ text: String) {
    super.init(frame: CGRect(x: 0, y: 0, width: 0, height: 0))

    self.text = text
    self.textColor = TEXT_COLOR
    textAlignment = .center
    self.layer.backgroundColor = BACKGROUND_COLOR
    self.layer.cornerRadius = 5
}

public func show(_ parent: UIViewController) {
    frame = CGRect(x: SIDE_MARGIN, y: UIScreen.main.bounds.height - BOTTOM_MARGIN - HEIGHT, width: UIScreen.main.bounds.width - 2 * SIDE_MARGIN, height: HEIGHT)

    if Toast.showing == nil {
        Log.d("showing \(String(describing: text))")
        Toast.showing = self
        alpha = 0
        parent.view.addSubview(self)
        UIView.animate(withDuration: ANIMATION_DURATION_SEC, animations: {
            self.alpha = 1
        }, completion: { (completed) in
            Timer.scheduledTimer(timeInterval: self.SHOW_TIME_SECONDS, target: self, selector: #selector(self.onTimeout), userInfo: nil, repeats: false)
        })
    } else {
        Toast.queue.append(ToastHolder(self, parent))
    }
}

@objc func onTimeout() {        
    UIView.animate(withDuration: ANIMATION_DURATION_SEC, animations: {
        self.alpha = 0
    }, completion: { (completed) in
        Toast.showing = nil
        self.removeFromSuperview()

        if !Toast.queue.isEmpty {
            let holder = Toast.queue.removeFirst()
            holder.toast.show(holder.parent)
        }
    })
}

required init?(coder aDecoder: NSCoder) {
    fatalError("this initializer is not supported")
}

private class ToastHolder {
    let toast: Toast
    let parent: UIViewController

    init(_ t: Toast, _ p: UIViewController) {
        toast = t
        parent = p
    }
}
}

Usage:

Toast("my message").show(self)

Hope it helps someone.

  • This is Nice w/ the Queuing of messages one after another to show! Although the solution from https://stackoverflow.com/a/50710991/14414215 has the auto-layout and auto-wrapping of a long message. – app4g Mar 28 '21 at 13:23
2

How about use Toaster

At a Glance

Toast(text: "Hello, world!").show()

Setting delay and duration

Toast(text: "Hello, world!", duration: Delay.long)
Toast(text: "Hello, world!", delay: Delay.short, duration: Delay.long)

Removing Toast

let toast = Toast(text: "Hello")
toast.show()
toast.cancel() // remove toast immediately

Customizing Appearance

  • backgroundColor
  • cornerRadius
  • textInsets
  • textColor
  • font
  • bottomOffsetPortrait
  • bottomOffsetLandscape
  • shadowPath
  • shadowColor
  • shadowOpacity
  • shadowOffset
  • shadowRadius
  • maxWidthRatio
  • useSafeAreaForBottomOffset
Cruz
  • 2,602
  • 19
  • 29
1

Instead of using UILabel using UITextView gets better results.

func showToast(message: String) {
        let toastLabel = UITextView(frame: CGRect(x: self.view.frame.size.width/16, y: self.view.frame.size.height-150, width: self.view.frame.size.width * 7/8, height: 35))
        toastLabel.backgroundColor = UIColor.black.withAlphaComponent(0.6)
        toastLabel.textColor = UIColor.white
        toastLabel.textAlignment = .center;
        toastLabel.text = "   \(message)   "
        toastLabel.alpha = 1.0
        toastLabel.layer.cornerRadius = 10;
        toastLabel.clipsToBounds  =  true
        toastLabel.font = UIFont(name: (toastLabel.font?.fontName)!, size: 16)
        toastLabel.layoutEdgeInsets.left = 8
        toastLabel.layoutEdgeInsets.right = 8
        toastLabel.center.x = self.view.frame.size.width/2
        self.view.addSubview(toastLabel)
        UIView.animate(withDuration: 5.0, delay: 0.1, options: .curveEaseOut, animations: {
            toastLabel.alpha = 0.0
        }, completion: {(isCompleted) in
            toastLabel.removeFromSuperview()
        })
}

Space is added with message to provide good spacing at the both ends so that it looks good. Modified version of answer of Mr.Bean

JavaGhost
  • 406
  • 4
  • 8
1
extension UIViewController {

func showToast(message:String,color:UIColor) {
    DispatchQueue.main.async {
        let toastLbl = UILabel(frame: CGRect(x: 20, y: self.view.frame.size.height - 100, width: self.view.frame.width - 40 , height: 30))
        toastLbl.layer.cornerRadius = 8;
        toastLbl.clipsToBounds  =  true
        toastLbl.textColor = .white
        toastLbl.font = .systemFont(ofSize: 15)
        toastLbl.textAlignment = .center;
        toastLbl.text = message
        self.view.addSubview(toastLbl)
        toastLbl.backgroundColor = color
        UIView.animate(withDuration: 2.0, delay: 0, options: .transitionCurlDown, animations: {
        }, completion: {(isCompleted) in
            toastLbl.removeFromSuperview()
        })
    }
} }
self.view.showToast(message: "Toast Shown", color: .green)
Sasinderan N
  • 77
  • 1
  • 3
  • For dynamic height based on content: let padding: CGFloat = 8 let maxSize = CGSize(width: self.view.frame.width - 40 - (2 * padding), height: .greatestFiniteMagnitude) let estimatedSize = toastLabel.sizeThatFits(maxSize) toastLabel.frame.size.height = estimatedSize.height + (2 * padding) – Sasinderan N May 16 '23 at 13:08
0
static func popUp(context ctx: UIViewController, msg: String) {

    let toast = UILabel(frame:
        CGRect(x: 16, y: ctx.view.frame.size.height / 2,
               width: ctx.view.frame.size.width - 32, height: 100))

    toast.backgroundColor = UIColor.lightGray
    toast.textColor = UIColor.white
    toast.textAlignment = .center;
    toast.numberOfLines = 3
    toast.font = UIFont.systemFont(ofSize: 20)
    toast.layer.cornerRadius = 12;
    toast.clipsToBounds  =  true

    toast.text = msg

    ctx.view.addSubview(toast)

    UIView.animate(withDuration: 5.0, delay: 0.2,
        options: .curveEaseOut, animations: {
        toast.alpha = 0.0
        }, completion: {(isCompleted) in
            toast.removeFromSuperview()
    })
}

Then just call it from UIViewController

popUp(context: self, msg: "Your message")
Andrew
  • 36,676
  • 11
  • 141
  • 113
0

Let me add this to this chain of answers: This library does what you need DCToastView allowing you to provide toast messages from the top or bottom side of the screen:

DCToastViewSample

You will just need to add the pod

pod 'DCToastView'

Import it where you want to use it.

import DCToastView

And use it

ToastPresenter.shared.show(in: self.view, message: "This is a toast")

You can pass the following properties to the show method:

  • view: The view in which the toast is going to be presented
  • message: The message that the toast will show
  • toastPlace: The place which can be .down or .up
  • backgroundColor: The background color of the toast; defaults to black
  • textColor: The text color of the message; defaults to white
  • timeOut: The amount of seconds for the toast to dismiss if not provided it means that the toast will be sticky (will remain until touched); defaults to nil
  • roundness: How round the toast will be: .none, .low, .mid, .high; defaults to .mid
vberihuete
  • 174
  • 5
0

This will help for you it will make the toast at the center with proper padding

func showToast(message:String,view:UIView){
    let toastLabel = PaddingLabel()
    toastLabel.frame = CGRect(x:0, y: view.frame.size.height-100, width: view.frame.width-50, height: 0)
    toastLabel.backgroundColor = UIColor.black.withAlphaComponent(0.6)
    toastLabel.textColor = UIColor.white
    toastLabel.textAlignment = .center;
    toastLabel.font = UIFont(name: "Montserrat-Light", size: 12.0)
    toastLabel.text = message
    toastLabel.alpha = 1.0
    toastLabel.layer.cornerRadius = 10;
    toastLabel.clipsToBounds  =  true
    toastLabel.sizeToFit()
    toastLabel.frame.origin.x=(view.frame.width/2)-(toastLabel.frame.width/2)
    view.addSubview(toastLabel)
    UIView.animate(withDuration: 4.0, delay: 0.1, options: .curveEaseOut, animations: {
        toastLabel.alpha = 0.0
    }, completion: {(isCompleted) in
        toastLabel.removeFromSuperview()
    })
}

And add this PaddingLabel file for label padding

import Foundation
import UIKit
class PaddingLabel: UILabel {

let padding=UIEdgeInsetsMake(5, 10, 5,10)
override func drawText(in rect: CGRect) {
    super.drawText(in: UIEdgeInsetsInsetRect(rect, padding))
}
override func sizeThatFits(_ size: CGSize) -> CGSize {
    let superSizeThatFits=super.sizeThatFits(size)
    let width=superSizeThatFits.width+padding.left+padding.right
    let height=superSizeThatFits.height+padding.top+padding.bottom
    return CGSize(width: width, height: height)
}
}
Arjun Othayoth
  • 351
  • 3
  • 5
0

Mr. Beans answer works well. However, his answer uses a small width & isn't multi-line friendly. Use this instead.

func showToastFaded(message : String) {


    let toastLabel = UILabel(frame: CGRect(x: self.view.frame.size.width/2 - 125, y: self.view.frame.size.height-100, width: 250, height: 35))
    toastLabel.numberOfLines = 0
    toastLabel.backgroundColor = UIColor.black.withAlphaComponent(0.6)
    toastLabel.textColor = UIColor.white
    toastLabel.textAlignment = .center;
    toastLabel.text = message
    toastLabel.alpha = 1.0
    toastLabel.layer.cornerRadius = 10;
    toastLabel.clipsToBounds  =  true
    toastLabel.sizeToFit()
    toastLabel.frame = CGRect( x: toastLabel.frame.minX, y: toastLabel.frame.minY,width:   toastLabel.frame.width + 20, height: toastLabel.frame.height + 8)

    self.view.addSubview(toastLabel)
    UIView.animate(withDuration: 4.0, delay: 0.1, options: .curveEaseOut, animations: {
        toastLabel.alpha = 0.0
    }, completion: {(isCompleted) in
        toastLabel.removeFromSuperview()
    })
}
grantespo
  • 2,233
  • 2
  • 24
  • 62
0

The best and easiest way to add a toast on an iOS app is using this library called Loafjet. The library is really small(27kb), won't make much difference to your project. Try it!

  • Agreed, but it depends on the developer how they adapt it, if they want then they can even refer from the code given in the library and try implementing it. Libraries are not only meant to use directly, they can even be used to learn new stuffs :) – Gokul Nair Sep 22 '21 at 17:46
0

I changed @Samo answer. Here it is, mine, quite simple. This is also landscape and long text friendly.

Toast.show(message: "Hello Toast", on: view)

..

class Toast {
    static func show(message: String, on baseView: UIView) {
        let containerView = UIView()
        containerView.backgroundColor = .black.withAlphaComponent(0.6)
        containerView.alpha = 0
        containerView.layer.cornerRadius = 25
        containerView.clipsToBounds = true

        let toastLabel = UILabel()
        toastLabel.textColor = .white
        toastLabel.textAlignment = .center
        toastLabel.font.withSize(12.0)
        toastLabel.text = message
        toastLabel.clipsToBounds = true
        toastLabel.numberOfLines = 0
        
        baseView.addSubview(containerView)
        containerView.translatesAutoresizingMaskIntoConstraints = false
        containerView.leadingAnchor.constraint(greaterThanOrEqualTo: baseView.leadingAnchor, constant: 65).isActive = true
        containerView.trailingAnchor.constraint(lessThanOrEqualTo: baseView.trailingAnchor, constant: -65).isActive = true
        containerView.bottomAnchor.constraint(equalTo: baseView.safeAreaLayoutGuide.bottomAnchor, constant: -10).isActive = true
        containerView.centerXAnchor.constraint(equalTo: baseView.centerXAnchor).isActive = true

        containerView.addSubview(toastLabel)
        toastLabel.translatesAutoresizingMaskIntoConstraints = false
        toastLabel.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 15).isActive = true
        toastLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 15).isActive = true
        toastLabel.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -15).isActive = true
        toastLabel.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -15).isActive = true

        UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseIn, animations: {
            containerView.alpha = 1
        }, completion: { _ in
            UIView.animate(withDuration: 0.5, delay: 1.5, options: .curveEaseOut, animations: {
                containerView.alpha = 0
            }, completion: { _ in
                containerView.removeFromSuperview()
            })
        })
    }
}
Yılmaz edis
  • 146
  • 3
  • 13
0

This is another way to do it.

    func showToast(viewController: UIViewController?, message: String) {
    let alertDisapperTimeInSeconds = 3.0
    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 = .systemFont(ofSize: 14)
    toastLabel.textAlignment = .center;
    toastLabel.text = message
    toastLabel.alpha = 1.0
    toastLabel.layer.cornerRadius = 10;
    toastLabel.clipsToBounds  =  true
    viewController?.view.addSubview(toastLabel)
    
    DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + alertDisapperTimeInSeconds) {
        toastLabel.alpha = 0.0
        toastLabel.removeFromSuperview()
    }
}

How to access function

self.showToast(viewController: self, message: "I am a Toast Message")
Niraj Paul
  • 1,498
  • 14
  • 22
0

Here is my solution. Hope it helps for someone..

class Toasty {
static let DELAY_SHORT = 2.0
static let DELAY_LONG = 4.0
static let BG_OPACITY: Double = 0.9
static let TOAST_HEIGHT = 20
static let TOAST_WIDTH = 24
static let TOAST_ADJUSTMENT = 96
static let FONT_SIZE = 16

enum Gravity {
    case bottom
    case center
    case top
}
enum Delay {
    case short
    case long
}

static func showToast(_ message: String, _ txtColor: UIColor, _ bgColor: UIColor, _ position: Gravity, _ delay: Delay) {
    guard let window = UIApplication.shared.keyWindow else {
        return
    }
    
    let label = ToastLabel()
    label.textColor = txtColor
    label.backgroundColor = bgColor
    label.textAlignment = .center
    label.font = UIFont.systemFont(ofSize: CGFloat(FONT_SIZE))
    label.alpha = 0
    label.text = message
    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 = 1000
        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)
    
    if (position == Gravity.bottom) {
        label.frame = CGRect(x: CGFloat(TOAST_WIDTH), y: window.frame.height - CGFloat(TOAST_ADJUSTMENT), width: width, height: size.height + CGFloat(TOAST_HEIGHT))
    } else if (position == Gravity.center) {
        label.frame = CGRect(x: CGFloat(TOAST_WIDTH), y: window.frame.height / 2, width: width, height: size.height + CGFloat(TOAST_HEIGHT))
    } else if (position == Gravity.top) {
        label.frame = CGRect(x: CGFloat(TOAST_WIDTH), y: CGFloat(TOAST_ADJUSTMENT), width: width, height: size.height + CGFloat(TOAST_HEIGHT))
    }
    
    label.center.x = window.center.x
    label.layer.cornerRadius = min(label.frame.height / 2, 32)
    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 == Delay.long ? DELAY_LONG : DELAY_SHORT, options: .curveEaseOut, animations: {
            label.alpha = 0
        }, completion: {_ in
            label.removeFromSuperview()
        })
    })
}

class func regular(msg: String, position: Gravity, delay: Delay) {
    showToast(msg, UIColor(.white), UIColor(.black.opacity(BG_OPACITY)), position, delay)
}
class func info(msg: String, position: Gravity, delay: Delay) {
    showToast(String("\u{24D8}")+"  "+msg, UIColor.white, UIColor(Color(red: 0/255, green: 100/255, blue: 225/255).opacity(BG_OPACITY)), position, delay)
}
class func alert(msg: String, position: Gravity, delay: Delay) {
    showToast(String("\u{26A0}")+"  "+msg, UIColor.black, UIColor(Color(red: 255/255, green: 175/255, blue: 0/255).opacity(BG_OPACITY)), position, delay)
}
class func success(msg: String, position: Gravity, delay: Delay) {
    showToast(String("\u{2705}")+"  "+msg, UIColor.white, UIColor(Color(red: 0/255, green: 150/255, blue: 0/255).opacity(BG_OPACITY)), position, delay)
}
class func error(msg: String, position: Gravity, delay: Delay) {//2757
    showToast(String("\u{274C}")+"  "+msg, UIColor.white, UIColor(Color(red: 175/255, green: 0/255, blue: 0/255).opacity(BG_OPACITY)), position, delay)
} }

class ToastyLabel: 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))
} }

Usage:

@State var increment = 0
ZStack {
Button(action: {
   increment += 1
   if increment == 1 {
       Toasty.info(msg: "Info Toasty", position: .bottom, delay: .short)
   } else if increment == 2 {
       Toasty.alert(msg: "Alert Toasty", position: .top, delay: .short)
   } else if increment == 3 {
       Toasty.success(msg: "Success Toasty", position: .center, delay: .short)
   } else if increment == 4 {
       Toasty.error(msg: "Error Toasty", position: .bottom, delay: .short)
   } else if increment == 5 {
       Toasty.regular(msg: "Regular Toasty", position: .top, delay: .short)
       increment = 0
                    }
   }, label: {
       Text("Show Toasty")
           .padding(12)
           .background(RoundedRectangle(cornerRadius: 8)
           .stroke(Color.blue, lineWidth: 1))
   })}

Toasty.gif

Ashok
  • 55
  • 1
  • 10
0

Step 1: Create a new Swift file for your custom toast view:

class ToastView: UIView {
    private var messageLabel: UILabel!

    init(message: String) {
        super.init(frame: CGRect.zero)
        configureUI(message: message)
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    private func configureUI(message: String) {
        // Customize your toast view's appearance
        backgroundColor = UIColor.black.withAlphaComponent(0.8)
        layer.cornerRadius = 10
        clipsToBounds = true

        messageLabel = UILabel(frame: CGRect.zero)
        messageLabel.text = message
        messageLabel.textColor = UIColor.white
        messageLabel.numberOfLines = 0
        messageLabel.textAlignment = .center
        messageLabel.font = UIFont.systemFont(ofSize: 15)

        addSubview(messageLabel)
        messageLabel.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            messageLabel.topAnchor.constraint(equalTo: topAnchor, constant: 8),
            messageLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 16),
            messageLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -16),
            messageLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8)
        ])
    }
}

Step 2: To show the toast message, add the following extension to UIViewController:

extension UIViewController {
    func showToast(message: String, duration: TimeInterval = 2.0) {
        let toastView = ToastView(message: message)
        view.addSubview(toastView)
        toastView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            toastView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            toastView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -50),
            toastView.leadingAnchor.constraint(greaterThanOrEqualTo: view.leadingAnchor, constant: 20),
            toastView.trailingAnchor.constraint(lessThanOrEqualTo: view.trailingAnchor, constant: -20)
        ])

        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + duration) {
            UIView.animate(withDuration: 0.3, animations: {
                toastView.alpha = 0
            }, completion: { _ in
                toastView.removeFromSuperview()
            })
        }
    }
}

Step 3: Now, in your view controller, you can use the showToast method to display the toast message:

// Example usage in your UIViewController:
class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Call the showToast method to display a toast message
        showToast(message: "Hello, this is a toast message!")
    }
}
Niraj Paul
  • 1,498
  • 14
  • 22
-1

Swift 4.2 Very easy and super way

let toastLabel = UILabel()

toastLabel.lineBreakMode = .byWordWrapping
toastLabel.numberOfLines = 0
toastLabel.text = "Type your message you want to show in toast"
toastLabel.sizeToFit()
//MARK Resize the Label Frame
toastLabel.frame = CGRect(x: toastLabel.frame.origin.x, y: toastLabel.frame.origin.y, width: toastLabel.frame.size.width + 40, height: toastLabel.frame.size.height + 40)
self.view.addSubview(toastLabel)
Shakeel Ahmed
  • 5,361
  • 1
  • 43
  • 34