How to hide label when its text is nil in swift - swift

I am adding badgeLabel to cartBtn.. here I am unable to convert integer to string and string to integer to hide badgeLabel if its count is 0
code:
public var badgeText: String? {
didSet {
let wasNil = NSString(string: badgeText ?? "0").integerValue <= 0
badgeLabel.isHidden = badgeText == nil
badgeLabel.text = badgeText
setSize()
if animated {
let animations: () -> Void = { [weak badgeLabel] in
badgeLabel?.layer.transform = CATransform3DMakeScale(1, 1, 1)
}
if wasNil {
badgeLabel.layer.transform = CATransform3DMakeScale(0.1, 0.1, 0.1)
}
UIView.animate(withDuration: 0.5,
delay: 0.2,
usingSpringWithDamping: 0.3,
initialSpringVelocity: 0.3,
options: UIView.AnimationOptions(),
animations: animations,
completion: nil)
}
}
}
public var badgeBackgroundColor = UIColor.red {
didSet {
badgeLabel.backgroundColor = badgeBackgroundColor
}
}
like this adding count to label
let cartQty = UserDefaults.standard.value(forKey: "cartCount")
cartBtn.badgeText = cartQty as? String
here if cartBtn.badgeText is nil then also i am showing badgeLabel but if its nil i don't want to show badgeLabel.. how to do hat.. please do help

It looks like you're not actually using the animations when wasNil is true. Also don't use NSString, it's an older API now and you can just use a easy Int initializer to go from String to Int.
Here's some updated code:
public var badgeText: String? {
didSet {
// changing this variable name for clarity.
let isTextAString: Bool = Int(badgeText ?? "") == nil
badgeLabel.isHidden = isTextAString
badgeLabel.text = badgeText
setSize()
if animated {
UIView.animate(withDuration: 0.5,
delay: 0.2,
usingSpringWithDamping: 0.3,
initialSpringVelocity: 0.3,
options: UIView.AnimationOptions()) {
self.badgeLabel?.layer.transform = CATransform3DMakeScale(1, 1, 1)
// You can just do this `if` statement inside the animation block
if isTextAString {
self.badgeLabel.layer.transform = CATransform3DMakeScale(0.1, 0.1, 0.1)
}
} completion: { (_) in }
}
}
}

Related

UIView on UITableViewCell updating without animating

I had a regression and I don't know what could have caused it.
Desired: smooth animation
Current: update without animation.
Here is the code that caused the break. I don't know why.
var pillModel: PillModel? {
didSet {
guard let pillModel = pillModel else { return }
DispatchQueue.main.async {
self.movePill(pillModel.side)
}
movingPill.backgroundColor = pillModel.movingPillColor
leftLabel.textColor = pillModel.leftLabelColor
rightLabel.textColor = pillModel.rightLabelColor
leftLabel.text = pillModel.leftTekst
rightLabel.text = pillModel.rightTekst
movingPill.layer.applySketchShadow(color: movingPill.backgroundColor!, alpha: 0.7, y: 3)
pillContainer.layoutSubviews()
commonStyle()
}
}
Here is some relevant code.
func movePill( _ sideTouched: Side, _ completion: (() -> ())? = nil) {
constrainPillTo(sideTouched)
pillModel?.side = sideTouched
UIView.animate(withDuration: 0.85, delay: 0, usingSpringWithDamping: 0.65, initialSpringVelocity: 1, options: .transitionCrossDissolve, animations: {
[weak self] in
guard let selfy = self else {return}
selfy.movingPill.backgroundColor = .orange//selfy.pillModel?.movingPillColor
selfy.leftLabel.textColor = .yellow//selfy.pillModel?.leftLabelColor
selfy.rightLabel.textColor = .green//selfy.pillModel?.rightLabelColor
selfy.movingPill.layer.applySketchShadow(color: selfy.movingPill.backgroundColor ?? .socialBlue,
alpha: 0.7, y: 3)
//selfy.pillContainer.layoutSubviews()
}) { _ in
completion?()
}
}
It turns out I created a cycle. When I moved the code out of the pillModel property's didSet method into its own function, the issue was resolved completely.

How can i set the same IBAction to multiple buttons?

I'm trying to link multiple buttons with the same IBAction, to run similar but different code. The code is to set an image that was clicked on another view controller into the UIImageView under the button.
All the buttons link to the same view controller but with a different segue.
I tried to write a if statements but I didn't seem to have it right. I have named each corresponding UIImage view: technologyImageViewTwo, technologyImageViewThree ...etc
below is the code I used for the first button which works with the corresponding UIImageView named technologyImageView
#IBAction func setTechnology(segue:UIStoryboardSegue) {
dismiss(animated: true) {
if let technology = segue.identifier{
self.persona.technology = technology
self.technologyView.technologyImageView.image = UIImage(named: technology)
}
//animating scale up of image
let scaleUp = CGAffineTransform.init(scaleX: 0.1, y:0.1)
self.technologyView.technologyImageView.transform = scaleUp
self.technologyView.technologyImageView.alpha = 0
//animating bounce effect
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0.7, options: [], animations: {
self.technologyView.technologyImageView.transform = .identity
self.technologyView.technologyImageView.alpha = 1
}, completion: nil)
}
I expect that each button should go to the segued view controller and the image selected will show up under the corresponding button. E.g if I click on the button 'Technology 2' and choose an image, the image shows up in the UIImageview named technologyImageViewTwo.
There are a couple of options, that you can choose from:
Option 1:
This option would be more preferred, that is to use the tag property on the component, this will allow you to identify the index of the button when it has been actioned.
https://developer.apple.com/documentation/uikit/uiview/1622493-tag
#IBAction func action(_ sender: Any) {
dismiss(animated: true) {
var imageView: UIImageView!
let index = (sender as? UIView)?.tag ?? 0
switch index {
case 1:
persona.technology = <#T##String#>
imageView = technologyView.technologyImageViewTwo
case 2:
persona.technology = <#T##String#>
imageView = technologyView.technologyImageViewThree
default:
persona.technology = <#T##String#>
imageView = technologyView.technologyImageView
}
if let technology = persona.technology {
imageView.image = UIImage(named: technology)
}
//animating scale up of image
let scaleUp = CGAffineTransform.init(scaleX: 0.1, y:0.1)
imageView.transform = scaleUp
imageView.alpha = 0
//animating bounce effect
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0.7, options: [], animations: {
imageView.transform = .identity
imageView.alpha = 1
})
}
}
Option 2:
If you want to continue with the code you have then you could use a segue identifier that contains components that you can be split out and enable you to identify them more efficiently:
segueIdentifier-1, segueIdentifier-2, segueIdentifier-3
func setTechnology(segue: UIStoryboardSegue) {
dismiss(animated: true) {
var imageView: UIImageView!
let identifierComponents = segue.identifier?.components(separatedBy: "-")
let index = Int(identifierComponents?.last ?? "0")
switch index {
case 1:
imageView = technologyView.technologyImageViewTwo
case 2:
imageView = technologyView.technologyImageViewThree
default:
imageView = technologyView.technologyImageView
}
if let technology = identifierComponents?.first {
self.persona.technology = technology
imageView.image = UIImage(named: technology)
}
//animating scale up of image
let scaleUp = CGAffineTransform.init(scaleX: 0.1, y:0.1)
imageView.transform = scaleUp
imageView.alpha = 0
//animating bounce effect
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0.7, options: [], animations: {
imageView.transform = .identity
imageView.alpha = 1
})
}
}
You could use three separate IBActions that all call the same function with the UIImageView number as a parameter.
func setTechnology(segue:UIStoryboardSegue, imageViewNumber: Int) {
dismiss(animated: true) {
var imageView: UIImageView!
if imageViewNumber == 0 {
imageView = self.technologyView.technologyImageView
} else if imageView == 1 {
imageView = self.technologyView.technologyImageViewTwo
} else {
imageView = self.technologyView.technologyImageViewThree
}
if let technology = segue.identifier{
self.persona.technology = technology
imageView.image = UIImage(named: technology)
}
//animating scale up of image
let scaleUp = CGAffineTransform.init(scaleX: 0.1, y:0.1)
imageView.transform = scaleUp
imageView.alpha = 0
//animating bounce effect
UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0.7, options: [], animations: {
imageView.transform = .identity
imageView.alpha = 1
}, completion: nil)
}
And then call this function from inside the three separate IBActions.
#IBAction func tappedButtonZero(segue:UIStoryboardSegue) {
self.setTechnology(segue: segue, imageViewNumber: 0)
}
#IBAction func tappedButtonOne(segue:UIStoryboardSegue) {
self.setTechnology(segue: segue, imageViewNumber: 1)
}
#IBAction func tappedButtonTwo(segue:UIStoryboardSegue) {
self.setTechnology(segue: segue, imageViewNumber: 2)
}
You could drag multiple buttons to your #IBAction and assign a unique tag value to each button. Then, use a switch statement to do whatever's unique to the button you pressed.
#IBAction func tappedButton(_ sender: UIButton) {
switch sender.tag {
case 1:
print("one")
case 2:
print("two")
case 3:
print("three")
default:
break
}
}

Multiple transforms UIView

I want to transform my image view like this:
I use landscape mode, I want the panda begin at the center vertically from the left, then it will rotate 360 again and again with become bigger and bigger, and go to the center vertically to the right.
I have tried like this:
import UIKit
class ViewController: UIViewController {
#IBOutlet var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
let scale = CGAffineTransformMakeScale(3.0, 3.0)
let rotate = CGAffineTransformMakeRotation(180*CGFloat(M_PI)/180)
let translate = CGAffineTransformMakeTranslation(UIScreen.mainScreen().bounds.width - 100, 0)
UIView.animateWithDuration(3.0, delay: 0, options: [.Repeat, .Autoreverse, .CurveEaseInOut], animations: { () -> Void in
let mixTransform = CGAffineTransformConcat(scale, translate)
self.imageView.transform = mixTransform
self.view.layoutIfNeeded()
}, completion: nil)
UIView.animateWithDuration(1.0, delay: 0, options: [.Repeat, .Autoreverse, .CurveEaseInOut], animations: { () -> Void in
self.imageView.transform = rotate
}, completion: nil)
}
}
But it doesn't work well.
Your transforms obviously dont fit your requirements.
If you want image to scale proportionally, why you set different scales for X and Y then?
Again, if you want image to move along X axis, why you adjust Y position of it in transforms? Simply fix your transform and you are ready to go.
In particular:
let scale = CGAffineTransformMakeScale(3.0, 3.0)
...
let translate = CGAffineTransformMakeTranslation(UIScreen.mainScreen().bounds.width - 400, 0)
Here is my answer:
import UIKit
import QuartzCore
class ViewController: UIViewController {
#IBOutlet var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
let kRotationAnimationKey = "com.myapplication.rotationanimationkey"
func rotateView(view: UIView, duration: Double = 1) {
if view.layer.animationForKey(kRotationAnimationKey) == nil {
let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotationAnimation.fromValue = 0.0
rotationAnimation.toValue = Float(M_PI * 2.0)
rotationAnimation.duration = duration
rotationAnimation.repeatCount = Float.infinity
view.layer.addAnimation(rotationAnimation, forKey: kRotationAnimationKey)
}
}
func stopRotatingView(view: UIView) {
if view.layer.animationForKey(kRotationAnimationKey) != nil {
view.layer.removeAnimationForKey(kRotationAnimationKey)
}
}
var transform = CGAffineTransformMakeTranslation(UIScreen.mainScreen().bounds.width - 200, 0)
transform = CGAffineTransformScale(transform, 3.0, 3.0)
UIView.animateWithDuration(1.0, delay: 0, options: [.Repeat, .Autoreverse, .CurveEaseInOut], animations: { () -> Void in
self.imageView.transform = transform
rotateView(self.imageView)
self.view.layoutIfNeeded()
}) { (_) -> Void in
stopRotatingView(self.imageView)
}
}
}
Result:

Trouble with Sprite Kit SKAction in Swift

I am having an issue with an SKAction sequence I set up.
The goal of this sequence is to take an array of 9 sprite nodes and display one at a time. This sequence will then show the node to the right or to the left of the current node depending on which button is pressed (right button or left button).
I am experiencing something interesting here. It seems to work as it is now, but if I press button left or button right multiple times fast, it seems like it cannot keep up and the process fails. I don't get an error, the sprite nodes just don't display or don't display correctly.
I have attached my code below. Because it is working when I cycle through slowly, I believe it is a timing issue and maybe I need to add a completion block. I tried doing this but was unsuccessful.
My question is, does anything obvious stick out that looks wrong here and do you think a completion block could possibly solve the issue?
func pressedButtonRight(){
if currentDisplayedWorld < 8 {
var currentWorld:SKSpriteNode = worldArray[currentDisplayedWorld] as SKSpriteNode
var nextWorld:SKSpriteNode = worldArray[currentDisplayedWorld + 1] as SKSpriteNode
nextWorld.position = CGPointMake(self.frame.size.width, 50)
self.addChild(nextWorld)
let move = SKAction.moveByX(-self.frame.size.width, y: 0,
duration: 1.0, delay: 0,
usingSpringWithDamping: 0.7, initialSpringVelocity: 0.5)
//currentWorld.runAction(move)
nextWorld.runAction(move)
currentWorld.removeFromParent()
currentDisplayedWorld++
}else if currentDisplayedWorld == 8 {
}
}
func pressedButtonLeft(){
if currentDisplayedWorld > 0 {
var currentWorld:SKSpriteNode = worldArray[currentDisplayedWorld] as SKSpriteNode
var previousWorld:SKSpriteNode = worldArray[currentDisplayedWorld - 1] as SKSpriteNode
previousWorld.position = CGPointMake(-self.frame.size.width, 50)
self.addChild(previousWorld)
let moveBack = SKAction.moveByX(self.frame.size.width, y: 0,
duration: 1.0, delay: 0,
usingSpringWithDamping: 0.7, initialSpringVelocity: 0.5)
//currentWorld.runAction(moveBack)
previousWorld.runAction(moveBack)
currentWorld.removeFromParent()
currentDisplayedWorld--
}else if currentDisplayedWorld == 0 {
}
}
Worked out perfect. I posted my final code below for all to see. Thanks again for the help.
func pressedButtonRight(){
if currentDisplayedWorld < 8 {
if self.moving == false {
self.moving = true
var currentWorld:SKSpriteNode = self.worldArray[currentDisplayedWorld] as SKSpriteNode
var nextWorld:SKSpriteNode = self.worldArray[currentDisplayedWorld + 1] as SKSpriteNode
nextWorld.position = CGPointMake(self.frame.size.width, 50)
self.addChild(nextWorld)
let move = SKAction.moveByX(-self.frame.size.width, y: 0, duration: 1.0, delay: 0,usingSpringWithDamping: 0.7, initialSpringVelocity: 0.5)
//currentWorld.runAction(move)
nextWorld.runAction(move, completion: {
self.moving = false
})
currentWorld.removeFromParent()
currentDisplayedWorld++
}
}else if currentDisplayedWorld == 8 {
}
}
func pressedButtonLeft(){
if currentDisplayedWorld > 0 {
if self.moving == false {
self.moving = true
var currentWorld:SKSpriteNode = self.worldArray[currentDisplayedWorld] as SKSpriteNode
var previousWorld:SKSpriteNode = self.worldArray[currentDisplayedWorld - 1] as SKSpriteNode
previousWorld.position = CGPointMake(-self.frame.size.width, 50)
self.addChild(previousWorld)
let moveBack = SKAction.moveByX(self.frame.size.width, y: 0, duration: 1.0, delay: 0,
usingSpringWithDamping: 0.7, initialSpringVelocity: 0.5)
//currentWorld.runAction(moveBack)
previousWorld.runAction(moveBack, completion: {
self.moving = false
})
currentWorld.removeFromParent()
currentDisplayedWorld--
}
}else if currentDisplayedWorld == 0 {
}
}

Growl/toast style notifications library for iOS

Can anyone recommend a library for implementing growl or toast-style notifications on iOS? For example, after a user saves a profile, I want to have a notification fade in, linger for 3 seconds, report "profile saved", and fade out. Right now I have a UIAlertView that interrupts the user's workflow with a single "OK" button, and I feel like that is overkill.
The Android Toast class is an example of what I am looking for on iOS.
Thanks!
I created a solution that I think you'll find useful:
https://github.com/scalessec/toast
It's written as a obj-c category, essentially adding makeToast methods to any instance of UIView. eg:
[self.view makeToast:#"Profile saved"
duration:2.0
position:#"bottom"];
I solved it this way:
Create common label on your view. Make it all screen wide, give it the size you will need and center text in it.
Set it's position "on top" - this label must be below all of your controls in the list of controls.
Add it to interface, properties, synthesize (let's call it "toastLabel" there).
Associate in your XIB file with "toastLabel"
Add following line to your viewWillAppear to hide label for beginning:
[toastLabel setHidden:TRUE];
Add the following code on Button click (or some other event):
toastLabel.text = #"Our toast text";
[toastLabel setHidden:TRUE];
[toastLabel setAlpha:1.0];
CGPoint location;
location.x = 160;
location.y = 220;
toastLabel.center = location;
location.x = 160;
location.y = 320;
[toastLabel setHidden:FALSE];
[UIView animateWithDuration:0.9 animations:^{
toastLabel.alpha = 0.0;
toastLabel.center = location;
}];
This label will "fall down" and disappear.
Albeit a little late, here's my take on it:
https://github.com/pcperini/PCToastMessage
You could try my open source library TSMessages: https://github.com/toursprung/TSMessages
It's really easy to use and looks beautiful on iOS 5/6 and on iOS 7 as well.
I made my own. The class linked to by Krishnan was ugly and didn't rotate correctly.
https://github.com/esilverberg/ios-toast
Here's what it looks like:
I made it in this way :
+ (void)showToastMessage:(NSString *)message {
UIAlertView *toast = [[UIAlertView alloc] initWithTitle:nil
message:message
delegate:nil
cancelButtonTitle:nil
otherButtonTitles:nil, nil];
[toast show];
// duration in seconds
int duration = 2;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, duration * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[toast dismissWithClickedButtonIndex:0 animated:YES];
});
}
Updated solution for iOS9+:
+ (void)showToastMessage:(NSString *)message
root:(id)view {
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil
message:message
preferredStyle:UIAlertControllerStyleAlert];
// duration in seconds
int duration = 2;
[view presentViewController:alertController animated:YES completion:nil];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, duration * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
[alertController dismissViewControllerAnimated:YES completion:nil];
});
}
MBProgressHUD?
Hey you are looking for this.
https://github.com/PaulSolt/WEPopover#readme
This one is exactly what you want.
This one is quite handy as well since it has a completion block, please have a look :) https://github.com/PrajeetShrestha/EkToast
Swift 2.0:
The idea is to work out a Toast class with zero dependency on CocoaPods.
Reference: https://github.com/scalessec/Toast-Swift
Make an empty Swift file (file-New-File- Empty Swift File - Name it Toast.)
Add the following code to it.
// Toast.swift
import UIKit
import ObjectiveC
enum ToastPosition {
case Top
case Center
case Bottom
}
extension UIView {
private struct ToastKeys {
static var Timer = "CSToastTimerKey"
static var Duration = "CSToastDurationKey"
static var Position = "CSToastPositionKey"
static var Completion = "CSToastCompletionKey"
static var ActiveToast = "CSToastActiveToastKey"
static var ActivityView = "CSToastActivityViewKey"
static var Queue = "CSToastQueueKey"
}
private class ToastCompletionWrapper {
var completion: ((Bool) -> Void)?
init(_ completion: ((Bool) -> Void)?) {
self.completion = completion
}
}
private enum ToastError: ErrorType {
case InsufficientData
}
private var queue: NSMutableArray {
get {
if let queue = objc_getAssociatedObject(self, &ToastKeys.Queue) as? NSMutableArray {
return queue
} else {
let queue = NSMutableArray()
objc_setAssociatedObject(self, &ToastKeys.Queue, queue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
return queue
}
}
}
// MARK: - Make Toast Methods
func makeToast(message: String) {
self.makeToast(message, duration: ToastManager.shared.duration, position: ToastManager.shared.position)
}
func makeToast(message: String, duration: NSTimeInterval, position: ToastPosition) {
self.makeToast(message, duration: duration, position: position, style: nil)
}
func makeToast(message: String, duration: NSTimeInterval, position: CGPoint) {
self.makeToast(message, duration: duration, position: position, style: nil)
}
func makeToast(message: String, duration: NSTimeInterval, position: ToastPosition, style: ToastStyle?) {
self.makeToast(message, duration: duration, position: position, title: nil, image: nil, style: style, completion: nil)
}
func makeToast(message: String, duration: NSTimeInterval, position: CGPoint, style: ToastStyle?) {
self.makeToast(message, duration: duration, position: position, title: nil, image: nil, style: style, completion: nil)
}
func makeToast(message: String?, duration: NSTimeInterval, position: ToastPosition, title: String?, image: UIImage?, style: ToastStyle?, completion: ((didTap: Bool) -> Void)?) {
var toastStyle = ToastManager.shared.style
if let style = style {
toastStyle = style
}
do {
let toast = try self.toastViewForMessage(message, title: title, image: image, style: toastStyle)
self.showToast(toast, duration: duration, position: position, completion: completion)
} catch ToastError.InsufficientData {
print("Error: message, title, and image are all nil")
} catch {}
}
func makeToast(message: String?, duration: NSTimeInterval, position: CGPoint, title: String?, image: UIImage?, style: ToastStyle?, completion: ((didTap: Bool) -> Void)?) {
var toastStyle = ToastManager.shared.style
if let style = style {
toastStyle = style
}
do {
let toast = try self.toastViewForMessage(message, title: title, image: image, style: toastStyle)
self.showToast(toast, duration: duration, position: position, completion: completion)
} catch ToastError.InsufficientData {
print("Error: message, title, and image cannot all be nil")
} catch {}
}
// MARK: - Show Toast Methods
func showToast(toast: UIView) {
self.showToast(toast, duration: ToastManager.shared.duration, position: ToastManager.shared.position, completion: nil)
}
func showToast(toast: UIView, duration: NSTimeInterval, position: ToastPosition, completion: ((didTap: Bool) -> Void)?) {
let point = self.centerPointForPosition(position, toast: toast)
self.showToast(toast, duration: duration, position: point, completion: completion)
}
func showToast(toast: UIView, duration: NSTimeInterval, position: CGPoint, completion: ((didTap: Bool) -> Void)?) {
objc_setAssociatedObject(toast, &ToastKeys.Completion, ToastCompletionWrapper(completion), .OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if let _ = objc_getAssociatedObject(self, &ToastKeys.ActiveToast) as? UIView where ToastManager.shared.queueEnabled {
objc_setAssociatedObject(toast, &ToastKeys.Duration, NSNumber(double: duration), .OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_setAssociatedObject(toast, &ToastKeys.Position, NSValue(CGPoint: position), .OBJC_ASSOCIATION_RETAIN_NONATOMIC);
self.queue.addObject(toast)
} else {
self.showToast(toast, duration: duration, position: position)
}
}
// MARK: - Activity Methods
func makeToastActivity(position: ToastPosition) {
// sanity
if let _ = objc_getAssociatedObject(self, &ToastKeys.ActiveToast) as? UIView {
return
}
let toast = self.createToastActivityView()
let point = self.centerPointForPosition(position, toast: toast)
self.makeToastActivity(toast, position: point)
}
func makeToastActivity(position: CGPoint) {
// sanity
if let _ = objc_getAssociatedObject(self, &ToastKeys.ActiveToast) as? UIView {
return
}
let toast = self.createToastActivityView()
self.makeToastActivity(toast, position: position)
}
//Dismisses the active toast activity indicator view.
func hideToastActivity() {
if let toast = objc_getAssociatedObject(self, &ToastKeys.ActivityView) as? UIView {
UIView.animateWithDuration(ToastManager.shared.style.fadeDuration, delay: 0.0, options: [.CurveEaseIn, .BeginFromCurrentState], animations: { () -> Void in
toast.alpha = 0.0
}, completion: { (finished: Bool) -> Void in
toast.removeFromSuperview()
objc_setAssociatedObject(self, &ToastKeys.ActivityView, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
})
}
}
// MARK: - Private Activity Methods
private func makeToastActivity(toast: UIView, position: CGPoint) {
toast.alpha = 0.0
toast.center = position
objc_setAssociatedObject(self, &ToastKeys.ActivityView, toast, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
self.addSubview(toast)
UIView.animateWithDuration(ToastManager.shared.style.fadeDuration, delay: 0.0, options: .CurveEaseOut, animations: { () -> Void in
toast.alpha = 1.0
}, completion: nil)
}
private func createToastActivityView() -> UIView {
let style = ToastManager.shared.style
let activityView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: style.activitySize.width, height: style.activitySize.height))
activityView.backgroundColor = style.backgroundColor
activityView.autoresizingMask = [.FlexibleLeftMargin, .FlexibleRightMargin, .FlexibleTopMargin, .FlexibleBottomMargin]
activityView.layer.cornerRadius = style.cornerRadius
if style.displayShadow {
activityView.layer.shadowColor = style.shadowColor.CGColor
activityView.layer.shadowOpacity = style.shadowOpacity
activityView.layer.shadowRadius = style.shadowRadius
activityView.layer.shadowOffset = style.shadowOffset
}
let activityIndicatorView = UIActivityIndicatorView(activityIndicatorStyle: .WhiteLarge)
activityIndicatorView.center = CGPoint(x: activityView.bounds.size.width / 2.0, y: activityView.bounds.size.height / 2.0)
activityView.addSubview(activityIndicatorView)
activityIndicatorView.startAnimating()
return activityView
}
// MARK: - Private Show/Hide Methods
private func showToast(toast: UIView, duration: NSTimeInterval, position: CGPoint) {
toast.center = position
toast.alpha = 0.0
if ToastManager.shared.tapToDismissEnabled {
let recognizer = UITapGestureRecognizer(target: self, action: "handleToastTapped:")
toast.addGestureRecognizer(recognizer)
toast.userInteractionEnabled = true
toast.exclusiveTouch = true
}
objc_setAssociatedObject(self, &ToastKeys.ActiveToast, toast, .OBJC_ASSOCIATION_RETAIN_NONATOMIC);
self.addSubview(toast)
UIView.animateWithDuration(ToastManager.shared.style.fadeDuration, delay: 0.0, options: [.CurveEaseOut, .AllowUserInteraction], animations: { () -> Void in
toast.alpha = 1.0
}) { (Bool finished) -> Void in
let timer = NSTimer(timeInterval: duration, target: self, selector: "toastTimerDidFinish:", userInfo: toast, repeats: false)
NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes)
objc_setAssociatedObject(toast, &ToastKeys.Timer, timer, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
private func hideToast(toast: UIView) {
self.hideToast(toast, fromTap: false)
}
private func hideToast(toast: UIView, fromTap: Bool) {
UIView.animateWithDuration(ToastManager.shared.style.fadeDuration, delay: 0.0, options: [.CurveEaseIn, .BeginFromCurrentState], animations: { () -> Void in
toast.alpha = 0.0
}) { (didFinish: Bool) -> Void in
toast.removeFromSuperview()
objc_setAssociatedObject(self, &ToastKeys.ActiveToast, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if let wrapper = objc_getAssociatedObject(toast, &ToastKeys.Completion) as? ToastCompletionWrapper, completion = wrapper.completion {
completion(fromTap)
}
if let nextToast = self.queue.firstObject as? UIView, duration = objc_getAssociatedObject(nextToast, &ToastKeys.Duration) as? NSNumber, position = objc_getAssociatedObject(nextToast, &ToastKeys.Position) as? NSValue {
self.queue.removeObjectAtIndex(0)
self.showToast(nextToast, duration: duration.doubleValue, position: position.CGPointValue())
}
}
}
// MARK: - Events
func handleToastTapped(recognizer: UITapGestureRecognizer) {
if let toast = recognizer.view, timer = objc_getAssociatedObject(toast, &ToastKeys.Timer) as? NSTimer {
timer.invalidate()
self.hideToast(toast, fromTap: true)
}
}
func toastTimerDidFinish(timer: NSTimer) {
if let toast = timer.userInfo as? UIView {
self.hideToast(toast)
}
}
// MARK: - Toast Construction
func toastViewForMessage(message: String?, title: String?, image: UIImage?, style: ToastStyle) throws -> UIView {
// sanity
if message == nil && title == nil && image == nil {
throw ToastError.InsufficientData
}
var messageLabel: UILabel?
var titleLabel: UILabel?
var imageView: UIImageView?
let wrapperView = UIView()
wrapperView.backgroundColor = style.backgroundColor
wrapperView.autoresizingMask = [.FlexibleLeftMargin, .FlexibleRightMargin, .FlexibleTopMargin, .FlexibleBottomMargin]
wrapperView.layer.cornerRadius = style.cornerRadius
if style.displayShadow {
wrapperView.layer.shadowColor = UIColor.blackColor().CGColor
wrapperView.layer.shadowOpacity = style.shadowOpacity
wrapperView.layer.shadowRadius = style.shadowRadius
wrapperView.layer.shadowOffset = style.shadowOffset
}
if let image = image {
imageView = UIImageView(image: image)
imageView?.contentMode = .ScaleAspectFit
imageView?.frame = CGRect(x: style.horizontalPadding, y: style.verticalPadding, width: style.imageSize.width, height: style.imageSize.height)
}
var imageRect = CGRectZero
if let imageView = imageView {
imageRect.origin.x = style.horizontalPadding
imageRect.origin.y = style.verticalPadding
imageRect.size.width = imageView.bounds.size.width
imageRect.size.height = imageView.bounds.size.height
}
if let title = title {
titleLabel = UILabel()
titleLabel?.numberOfLines = style.titleNumberOfLines
titleLabel?.font = style.titleFont
titleLabel?.textAlignment = style.titleAlignment
titleLabel?.lineBreakMode = .ByTruncatingTail
titleLabel?.textColor = style.titleColor
titleLabel?.backgroundColor = UIColor.clearColor();
titleLabel?.text = title;
let maxTitleSize = CGSize(width: (self.bounds.size.width * style.maxWidthPercentage) - imageRect.size.width, height: self.bounds.size.height * style.maxHeightPercentage)
let titleSize = titleLabel?.sizeThatFits(maxTitleSize)
if let titleSize = titleSize {
titleLabel?.frame = CGRect(x: 0.0, y: 0.0, width: titleSize.width, height: titleSize.height)
}
}
if let message = message {
messageLabel = UILabel()
messageLabel?.text = message
messageLabel?.numberOfLines = style.messageNumberOfLines
messageLabel?.font = style.messageFont
messageLabel?.textAlignment = style.messageAlignment
messageLabel?.lineBreakMode = .ByTruncatingTail;
messageLabel?.textColor = style.messageColor
messageLabel?.backgroundColor = UIColor.clearColor()
let maxMessageSize = CGSize(width: (self.bounds.size.width * style.maxWidthPercentage) - imageRect.size.width, height: self.bounds.size.height * style.maxHeightPercentage)
let messageSize = messageLabel?.sizeThatFits(maxMessageSize)
if let messageSize = messageSize {
messageLabel?.frame = CGRect(x: 0.0, y: 0.0, width: messageSize.width, height: messageSize.height)
}
}
var titleRect = CGRectZero
if let titleLabel = titleLabel {
titleRect.origin.x = imageRect.origin.x + imageRect.size.width + style.horizontalPadding
titleRect.origin.y = style.verticalPadding
titleRect.size.width = titleLabel.bounds.size.width
titleRect.size.height = titleLabel.bounds.size.height
}
var messageRect = CGRectZero
if let messageLabel = messageLabel {
messageRect.origin.x = imageRect.origin.x + imageRect.size.width + style.horizontalPadding
messageRect.origin.y = titleRect.origin.y + titleRect.size.height + style.verticalPadding
messageRect.size.width = messageLabel.bounds.size.width
messageRect.size.height = messageLabel.bounds.size.height
}
let longerWidth = max(titleRect.size.width, messageRect.size.width)
let longerX = max(titleRect.origin.x, messageRect.origin.x)
let wrapperWidth = max((imageRect.size.width + (style.horizontalPadding * 2.0)), (longerX + longerWidth + style.horizontalPadding))
let wrapperHeight = max((messageRect.origin.y + messageRect.size.height + style.verticalPadding), (imageRect.size.height + (style.verticalPadding * 2.0)))
wrapperView.frame = CGRect(x: 0.0, y: 0.0, width: wrapperWidth, height: wrapperHeight)
if let titleLabel = titleLabel {
titleLabel.frame = titleRect
wrapperView.addSubview(titleLabel)
}
if let messageLabel = messageLabel {
messageLabel.frame = messageRect
wrapperView.addSubview(messageLabel)
}
if let imageView = imageView {
wrapperView.addSubview(imageView)
}
return wrapperView
}
// MARK: - Helpers
private func centerPointForPosition(position: ToastPosition, toast: UIView) -> CGPoint {
let padding: CGFloat = ToastManager.shared.style.verticalPadding
switch(position) {
case .Top:
return CGPoint(x: self.bounds.size.width / 2.0, y: (toast.frame.size.height / 2.0) + padding)
case .Center:
return CGPoint(x: self.bounds.size.width / 2.0, y: self.bounds.size.height / 2.0)
case .Bottom:
return CGPoint(x: self.bounds.size.width / 2.0, y: (self.bounds.size.height - (toast.frame.size.height / 2.0)) - padding)
}
}
}
// MARK: - Toast Style
struct ToastStyle {
var backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.8)
var titleColor = UIColor.whiteColor()
var messageColor = UIColor.whiteColor()
var maxWidthPercentage: CGFloat = 0.8 {
didSet {
maxWidthPercentage = max(min(maxWidthPercentage, 1.0), 0.0)
}
}
var maxHeightPercentage: CGFloat = 0.8 {
didSet {
maxHeightPercentage = max(min(maxHeightPercentage, 1.0), 0.0)
}
}
var horizontalPadding: CGFloat = 10.0
var verticalPadding: CGFloat = 10.0
var cornerRadius: CGFloat = 10.0;
var titleFont = UIFont.boldSystemFontOfSize(16.0)
var messageFont = UIFont.systemFontOfSize(16.0)
var titleAlignment = NSTextAlignment.Left
var messageAlignment = NSTextAlignment.Left
var titleNumberOfLines = 0;
var messageNumberOfLines = 0;
var displayShadow = false;
var shadowColor = UIColor.blackColor()
var shadowOpacity: Float = 0.8 {
didSet {
shadowOpacity = max(min(shadowOpacity, 1.0), 0.0)
}
}
var shadowRadius: CGFloat = 6.0
var shadowOffset = CGSize(width: 4.0, height: 4.0)
var imageSize = CGSize(width: 80.0, height: 80.0)
var activitySize = CGSize(width: 100.0, height: 100.0)
var fadeDuration: NSTimeInterval = 0.2
}
// MARK: - Toast Manager
class ToastManager {
static let shared = ToastManager()
var style = ToastStyle()
var tapToDismissEnabled = true
var queueEnabled = true
var duration: NSTimeInterval = 3.0
var position = ToastPosition.Bottom
}
Using Toast.swift:
// basic usage
self.view.makeToast("Sample Toast")
// toast with a specific duration and position
self.view.makeToast("Sample Toast", duration: 3.0, position: .Top)
// toast with all possible options
self.view.makeToast("Sample Toast", duration: 2.0, position: CGPoint(x: 110.0, y: 110.0), title: "Toast Title", image: UIImage(named: "ic_120x120.png"), style:nil) { (didTap: Bool) -> Void in
if didTap {
print("completion from tap")
} else {
print("completion without tap")
}
}
//display toast with an activity spinner
self.view.makeToastActivity(.Center)
// display any view as toast
let sampleView = UIView(frame: CGRectMake(100,100,200,200))
sampleView.backgroundColor = UIColor(patternImage: UIImage(named: "ic_120x120")!)
self.view.showToast(sampleView)
self.view.showToast(sampleView, duration: 3.0, position: .Top, completion: nil)
You can download a sample project from https://github.com/alvinreuben/ToastSample
This is something, which was needed by you. Created with lot of Animation Options, screen positions, and duration. Even you can provide your own duration. Check that out below.
https://github.com/avistyles/AVIToast
Swift 3
Have been using Rannie/Toast-Swift for Swift 3 very happily for a while now and can recommend it for a very similar "Android-like" experience. Its very simple to implement without needing another pod and pretty customizable depending on your needs.
Easy Peasy
view.makeToastActivity()
view.hideToastActivity()