Looping with animations - swift

I have this code
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//'img' is a picture imported in your project
self.mrock.image = UIImage(named: "bird")
self.rrock.image = UIImage(named: "rock2")
self.lrock.image = UIImage(named: "rock3")
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if rockNamesArray[1] == "rock2" {
let firstPos: CGFloat = 300.0
UIView.animateWithDuration(3.0, animations: { () -> Void in
self.mrock.frame = CGRectMake(167, 600, CGFloat(self.mrock.bounds.size.width), CGFloat(self.mrock.bounds.size.height))
})}
if rockNamesArray[2] == "rock3" {
let secondPos: CGFloat = 300.0
UIView.animateWithDuration(3.0, animations: { () -> Void in
self.rrock.frame = CGRectMake(220, 600, CGFloat(self.rrock.bounds.size.width), CGFloat(self.rrock.bounds.size.height))
})}
if rockNamesArray[0] == "bird" {
let thirdPos: CGFloat = 300.0
UIView.animateWithDuration(3.0, animations: { () -> Void in
self.lrock.frame = CGRectMake(115, 600, CGFloat(self.lrock.bounds.size.width), CGFloat(self.lrock.bounds.size.height))
})}
And I would like to know how to make it so that when the png reaches the end of the screen, that it goes back to its original position, how would I do this?

If I understand your question correctly, you can pass a completion closure to UIView.animateWithDuration that will be executed once your animation is complete. In the closure you can make the view return to its original position, for example:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let originalFrame = lrock.frame
UIView.animateWithDuration(3.0, animations: {
lrock.frame = CGRect(origin: CGPoint(x: 115, y: 600), size: lrock.frame.size)
},
completion: { _ in
lrock.frame = originalFrame
})
}

Related

How to stop a Lottie animation manually

I use the Lottie animation library in XCode. But when I go to another page, the animation is running in the background. When I go back the main page, the animation is running twice and there is too much RAM being consumed. How can I fix this problem? and how can I stop the animation after a specific time?
import UIKit
import Lottie
class CoffeeSpecialFortunePageVC: UIViewController {
#IBAction func PopUpIn(_ sender: Any) {
animateIn(desiredView: PopupBlur)
animateIn(desiredView: popupView)
animation()
}
#IBAction func SuccesBtn(_ sender: UIView) {
animateOut(desiredView: PopupBlur)
animateOut(desiredView: popupView)
}
#IBOutlet var popupView: AnimationView!
#IBOutlet var PopupBlur: UIVisualEffectView!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.purple
PopupBlur.bounds = self.view.bounds
popupView.bounds = CGRect(x: 0, y: 0, width: self.view.bounds.width * 0.9, height: self.view.bounds.height * 0.4)
// Do any additional setup after loading the view.
}
func animation() {
popupView! = .init(name: "check1")
popupView?.frame = view.bounds
view.addSubview(popupView!)
popupView?.play()
popupView.contentMode = .scaleAspectFit
popupView.frame.size.height = 350
popupView.frame.size.width = 350
popupView.center.x = view.center.x
popupView.center.y = view.center.y
}
func animateIn(desiredView: UIView) {
let backgraundview = self.view!
backgraundview.addSubview(desiredView)
desiredView.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
desiredView.alpha = 0
desiredView.center = backgraundview.center
UIView.animate(withDuration: 0.3, animations:{
desiredView.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
desiredView.alpha = 1
})
}
func animateOut(desiredView: UIView) {
UIView.animate(withDuration: 0.3, animations: {
desiredView.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
desiredView.alpha = 0
},completion: {_ in
desiredView.removeFromSuperview()
})
}
}
You can use one of the play(fromProgress...) API's that Lottie provides
animationView.play(
fromProgress: animationView.currentProgress,
toProgress: 1,
loopMode: .playOnce,
completion: { [weak self] completed in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
self.stopLoopAndTranstion(to: animation, endIn: image)
}
}
)
This will ensure that your current animation will finish and in completion block you can do your finishing work
One of the best solutions:
Create a notifier when the app goes to the background for pause and when back to the foreground
override func viewDidLoad()
{
super.viewDidLoad()
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(appMovedToBackground), name: UIApplication.willResignActiveNotification, object: nil)
notificationCenter.addObserver(self, selector: #selector(appBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil)
animationView = .init(name: "lang")
animationView!.frame = CGRect(x: 20, y: 100, width: 50, height: 50)
animationView!.contentMode = .scaleAspectFit
animationView!.loopMode = .loop
animationView!.animationSpeed = 0.5
view.addSubview(animationView!)
animationView!.play()
}
This will invoke notification methods when the app moves to background and foreground
#objc func appMovedToBackground()
{
print("App moved to background!")
animationView!.removeFromSuperview()
}
#objc func appBecomeActive()
{
print("App become active")
//animationView!.play()
animationView = .init(name: "lang")
animationView!.frame = CGRect(x: 20, y: 100, width: 50, height: 50)
animationView!.contentMode = .scaleAspectFit
animationView!.loopMode = .loop
animationView!.animationSpeed = 0.5
view.addSubview(animationView!)
animationView!.play()
}
This will solve your problem....... For sure

How to transform an NSImageView in cocoa?

I'm struggling with cocoa. I'm trying to write this piece of animation from iOS into cocoa. The idea is to slightly decrease the size of the NSImageView and after the animation completes, increase it again to the original size. So that it looks as if the button (picture) was pressed.
#IBOutlet weak var vpnButton: NSImageView!
#objc func vpnButtonPressed(pressedGestureRecognizer: UILongPressGestureRecognizer) {
UserDefaults.standard.set(true, forKey: Constants.vpnButtonTapped)
if (pressedGestureRecognizer.state == .began) {
UIView.animate(withDuration: 0.15, animations: {() -> Void in
self.vpnButton?.transform = CGAffineTransform(scaleX: 0.965, y: 0.965)})
} else if (pressedGestureRecognizer.state == .ended) {
UIView.animate(withDuration: 0.15, animations: {() -> Void in
self.vpnButton.isHighlighted = !self.vpnButton.isHighlighted
self.vpnButton?.transform = CGAffineTransform(scaleX: 1, y: 1)})
}
}
In cocoa, I was able to find the clickGesture. I'm not sure if this is the best choice.
So I came up with this:
#objc func vpnButtonPressed(clickedGestureRecognizer: NSGestureRecognizer) {
UserDefaults.standard.set(true, forKey: Constants.vpnButtonTapped)
print("clicked")
NSAnimationContext.runAnimationGroup({_ in
//Indicate the duration of the animation
NSAnimationContext.current.duration = 0.5
var transform = CGAffineTransform(scaleX: 0.965, y: 0.965)
self.vpnButton.layer?.setAffineTransform(transform)
}, completionHandler:{
// var transform = self.vpnButton.layer?.affineTransform()
// transform = CGAffineTransform(scaleX: 1, y: 1)
// self.vpnButton.layer?.setAffineTransform(transform!)
print("Animation completed")
})
}
This worked only once by moving the image slightly aside, but it doesn't make it smaller. If I uncomment the three lines in the completion handler, I don't see the animation moving it back either.
As far as I understood you need something like the following (sending action itself, if needed, is out of scope - here just animation)
class MyImageView: NSImageView {
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
self.wantsLayer = true
}
required init?(coder: NSCoder) {
super.init(coder: coder)
self.wantsLayer = true
}
override func mouseDown(with event: NSEvent) {
NSAnimationContext.runAnimationGroup({ (context) in
context.duration = 0.15
self.layer?.setAffineTransform(CGAffineTransform(scaleX: 0.965, y: 0.965))
})
}
override func mouseUp(with event: NSEvent) {
NSAnimationContext.runAnimationGroup({ (context) in
context.duration = 0.15
self.layer?.setAffineTransform(.identity)
})
}
}

change position UiButton with Animation Swift

i have a button in ViewController and i changed button position when clicked on that .
the question it is how to can move button to new position when Clicked with animation ?
this is view Controller :
override func viewDidLoad() {
super.viewDidLoad()
let X = 50
let Y = 100
let WIDTH = 100
let HEIGHT = 100
movable_Button.frame = CGRect(x: X, y: Y, width: WIDTH , height: HEIGHT)
}
this is change position method When clicked :
#IBAction func movable_Button_DidTouch(_ sender: Any) {
movable_Button.frame = CGRect(x: X + 150, y: Y + 150, width: 100, height: 100)
}
Let's use UIView animate block
#IBAction func movableButtonDidTouch(_ sender: Any) {
// Set initial frame here
UIView.animate(withDuration: 0.35) {
// Set final frame here
}
}
Please notice to remove underline (_) in function name, it's not the convention of Swift, use camel case instead.
Update
class ViewController: UIViewController {
var positions = [CGPoint]()
let width = 100.0
let height = 50.0
var index = 0
lazy var button: UIButton = {
let theButton = UIButton(type: .system)
theButton.frame = CGRect(x: 50, y: 50, width: self.width, height: self.height)
theButton.backgroundColor = .red
return theButton
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.button)
for index in 1...4 {
let point = CGPoint(x: 50 * index, y: 50 * index)
self.positions.append(point)
}
}
#IBAction func changeButtonPosition(_ sender: AnyObject) {
UIView.animate(withDuration: 0.35) { [weak self] in
guard let self = self else { return }
self.index += 1
let point = self.positions[self.index % 4]
self.button.frame = CGRect(origin: point, size: CGSize(width: self.width, height: self.height))
}
}
}

Side panel in Swift iPad

I have issues with my side panel for iPad app. I need buttons stacked as below:
Expected Output:
Right now, my output produces:
Current Output:
How can I remove circles and add button sets?
import UIKit
import QuartzCore
public protocol FrostedSidebarDelegate{
func sidebar(sidebar: FrostedSidebar, willShowOnScreenAnimated animated: Bool)
func sidebar(sidebar: FrostedSidebar, didShowOnScreenAnimated animated: Bool)
func sidebar(sidebar: FrostedSidebar, willDismissFromScreenAnimated animated: Bool)
func sidebar(sidebar: FrostedSidebar, didDismissFromScreenAnimated animated: Bool)
func sidebar(sidebar: FrostedSidebar, didTapItemAtIndex index: Int)
func sidebar(sidebar: FrostedSidebar, didEnable itemEnabled: Bool, itemAtIndex index: Int)
}
var sharedSidebar: FrostedSidebar?
public enum SidebarItemSelectionStyle{
case None
se Single
case All
}
public class FrostedSidebar: UIViewController {
public var width: CGFloat = 300.0
/**
If the sidebar should show from the right.
*/
public var showFromRight: Bool = false
/**
The speed at which the sidebar is presented/dismissed.
*/
public var animationDuration: CGFloat = 0.25
/**
The size of the sidebar items.
*/
public var itemSize: CGSize = CGSize(width: 200.0, height: 200.0)
/**
The background color of the sidebar items.
*/
public var itemBackgroundColor: UIColor = UIColor(white: 1, alpha: 0.25)
/**
The width of the ring around selected sidebar items.
*/
public var borderWidth: CGFloat = 2
/**
The sidebar's delegate.
*/
public var delegate: FrostedSidebarDelegate? = nil
/**
A dictionary that holds the actions for each item index.
*/
public var actionForIndex: [Int : ()->()] = [:]
/**
The indexes that are selected and have rings around them.
*/
public var selectedIndices: NSMutableIndexSet = NSMutableIndexSet()
/**
If the sidebar should be positioned beneath a navigation bar that is on screen.
*/
public var adjustForNavigationBar: Bool = false
/**
Returns whether or not the sidebar is currently being displayed
*/
public var isCurrentlyOpen: Bool = false
/**
The selection style for the sidebar.
*/
public var selectionStyle: SidebarItemSelectionStyle = .None{
didSet{
if case .All = selectionStyle{
selectedIndices = NSMutableIndexSet(indexesInRange: NSRange(location: 0, length: images.count))
}
}
}
//MARK: Private Properties
private var contentView: UIScrollView = UIScrollView()
private var blurView: UIVisualEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .Dark))
private var dimView: UIView = UIView()
private var tapGesture: UITapGestureRecognizer? = nil
private var images: [UIImage] = []
private var borderColors: [UIColor]? = nil
private var itemViews: [CalloutItem] = []
//MARK: Public Methods
/**
Returns an object initialized from data in a given unarchiver.
*/
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
/**
Returns a sidebar initialized with the given data.
- Parameter itemImages: The images that will be used for each item.
- Parameter colors: The color of rings around each image.
- Parameter selectionStyle: The selection style for the sidebar.
- Precondition: `colors` is either `nil` or contains the same number of elements as `itemImages`.
*/
public init(itemImages: [UIImage], colors: [UIColor]?, selectionStyle: SidebarItemSelectionStyle){
contentView.alwaysBounceHorizontal = false
contentView.alwaysBounceVertical = true
contentView.bounces = true
contentView.clipsToBounds = false
contentView.showsHorizontalScrollIndicator = false
contentView.showsVerticalScrollIndicator = false
if let colors = colors{
assert(itemImages.count == colors.count, "If item color are supplied, the itemImages and colors arrays must be of the same size.")
}
self.selectionStyle = selectionStyle
borderColors = colors
images = itemImages
for (index, image) in images.enumerate(){
let view = CalloutItem(index: index)
view.clipsToBounds = true
view.imageView.image = image
contentView.addSubview(view)
itemViews += [view]
if let borderColors = borderColors{
if selectedIndices.containsIndex(index){
let color = borderColors[index]
view.layer.borderColor = color.CGColor
}
} else{
view.layer.borderColor = UIColor.clearColor().CGColor
}
}
super.init(nibName: nil, bundle: nil)
}
public override func shouldAutorotate() -> Bool {
return true
}
public override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
return UIInterfaceOrientationMask.All
}
public override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)
if isViewLoaded(){
dismissAnimated(false, completion: nil)
}
}
public override func loadView() {
super.loadView()
view.backgroundColor = UIColor.clearColor()
view.addSubview(dimView)
view.addSubview(blurView)
view.addSubview(contentView)
tapGesture = UITapGestureRecognizer(target: self, action: #selector(FrostedSidebar.handleTap(_:)))
view.addGestureRecognizer(tapGesture!)
}
/**
Shows the sidebar in a view controller.
- Parameter viewController: The view controller in which to show the sidebar.
- Parameter animated: If the sidebar should be animated.
*/
public func showInViewController(viewController: UIViewController, animated: Bool){
layoutItems()
if let bar = sharedSidebar{
bar.dismissAnimated(false, completion: nil)
}
delegate?.sidebar(self, willShowOnScreenAnimated: animated)
sharedSidebar = self
addToParentViewController(viewController, callingAppearanceMethods: true)
view.frame = viewController.view.bounds
dimView.backgroundColor = UIColor.blackColor()
dimView.alpha = 0
dimView.frame = view.bounds
let parentWidth = view.bounds.size.width
var contentFrame = view.bounds
contentFrame.origin.x = showFromRight ? parentWidth : -width
contentFrame.size.width = width
contentView.frame = contentFrame
contentView.contentOffset = CGPoint(x: 0, y: 0)
layoutItems()
var blurFrame = CGRect(x: showFromRight ? view.bounds.size.width : 0, y: 0, width: 0, height: view.bounds.size.height)
blurView.frame = blurFrame
blurView.contentMode = showFromRight ? UIViewContentMode.TopRight : UIViewContentMode.TopLeft
blurView.clipsToBounds = true
view.insertSubview(blurView, belowSubview: contentView)
contentFrame.origin.x = showFromRight ? parentWidth - width : 0
blurFrame.origin.x = contentFrame.origin.x
blurFrame.size.width = width
let animations: () -> () = {
self.contentView.frame = contentFrame
self.blurView.frame = blurFrame
self.dimView.alpha = 0.25
}
let completion: (Bool) -> Void = { finished in
if finished{
self.delegate?.sidebar(self, didShowOnScreenAnimated: animated)
}
}
if animated{
UIView.animateWithDuration(NSTimeInterval(animationDuration), delay: 0, options: UIViewAnimationOptions(), animations: animations, completion: completion)
} else{
animations()
completion(true)
}
for (index, item) in itemViews.enumerate(){
item.layer.transform = CATransform3DMakeScale(0.3, 0.3, 1)
item.alpha = 0
item.originalBackgroundColor = itemBackgroundColor
item.layer.borderWidth = borderWidth
animateSpringWithView(item, idx: index, initDelay: animationDuration)
}
self.isCurrentlyOpen = true
}
/**
Dismisses the sidebar.
- Parameter animated: If the sidebar should be animated.
- Parameter completion: Completion handler called when the sidebar is dismissed.
*/
public func dismissAnimated(animated: Bool, completion: ((Bool) -> Void)?){
let completionBlock: (Bool) -> Void = {finished in
self.removeFromParentViewControllerCallingAppearanceMethods(true)
self.delegate?.sidebar(self, didDismissFromScreenAnimated: true)
self.layoutItems()
if let completion = completion{
completion(finished)
}
}
delegate?.sidebar(self, willDismissFromScreenAnimated: animated)
if animated{
let parentWidth = view.bounds.size.width
var contentFrame = contentView.frame
contentFrame.origin.x = showFromRight ? parentWidth : -width
var blurFrame = blurView.frame
blurFrame.origin.x = showFromRight ? parentWidth : 0
blurFrame.size.width = 0
UIView.animateWithDuration(NSTimeInterval(animationDuration), delay: 0, options: UIViewAnimationOptions.BeginFromCurrentState, animations: {
self.contentView.frame = contentFrame
self.blurView.frame = blurFrame
self.dimView.alpha = 0
}, completion: completionBlock)
} else{
completionBlock(true)
}
self.isCurrentlyOpen = false
}
/**
Selects the item at the given index.
- Parameter index: The index of the item to select.
*/
public func selectItemAtIndex(index: Int){
let didEnable = !selectedIndices.containsIndex(index)
if let borderColors = borderColors{
let stroke = borderColors[index]
let item = itemViews[index]
if didEnable{
if case .Single = selectionStyle{
selectedIndices.removeAllIndexes()
for item in itemViews{
item.layer.borderColor = UIColor.clearColor().CGColor
}
}
item.layer.borderColor = stroke.CGColor
let borderAnimation = CABasicAnimation(keyPath: "borderColor")
borderAnimation.fromValue = UIColor.clearColor().CGColor
borderAnimation.toValue = stroke.CGColor
borderAnimation.duration = 0.5
item.layer.addAnimation(borderAnimation, forKey: nil)
selectedIndices.addIndex(index)
} else{
if case .None = selectionStyle{
item.layer.borderColor = UIColor.clearColor().CGColor
selectedIndices.removeIndex(index)
}
}
let pathFrame = CGRect(x: -CGRectGetMidX(item.bounds), y: -CGRectGetMidY(item.bounds), width: item.bounds.size.width, height: item.bounds.size.height)
let path = UIBezierPath(roundedRect: pathFrame, cornerRadius: item.layer.cornerRadius)
let shapePosition = view.convertPoint(item.center, fromView: contentView)
let circleShape = CAShapeLayer()
circleShape.path = path.CGPath
circleShape.position = shapePosition
circleShape.fillColor = UIColor.clearColor().CGColor
circleShape.opacity = 0
circleShape.strokeColor = stroke.CGColor
circleShape.lineWidth = borderWidth
view.layer.addSublayer(circleShape)
let scaleAnimation = CABasicAnimation(keyPath: "transform.scale")
scaleAnimation.fromValue = NSValue(CATransform3D: CATransform3DIdentity)
scaleAnimation.toValue = NSValue(CATransform3D: CATransform3DMakeScale(2.5, 2.5, 1))
let alphaAnimation = CABasicAnimation(keyPath: "opacity")
alphaAnimation.fromValue = 1
alphaAnimation.toValue = 0
let animation = CAAnimationGroup()
animation.animations = [scaleAnimation, alphaAnimation]
animation.duration = 0.5
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
circleShape.addAnimation(animation, forKey: nil)
}
if let action = actionForIndex[index]{
action()
}
delegate?.sidebar(self, didTapItemAtIndex: index)
delegate?.sidebar(self, didEnable: didEnable, itemAtIndex: index)
}
//MARK: Private Classes
private class CalloutItem: UIView{
var imageView: UIImageView = UIImageView()
var itemIndex: Int
var originalBackgroundColor:UIColor? {
didSet{
backgroundColor = originalBackgroundColor
}
}
required init?(coder aDecoder: NSCoder) {
itemIndex = 0
super.init(coder: aDecoder)
}
init(index: Int){
imageView.backgroundColor = UIColor.clearColor()
imageView.contentMode = UIViewContentMode.ScaleAspectFit
itemIndex = index
super.init(frame: CGRect.zero)
addSubview(imageView)
}
override func layoutSubviews() {
super.layoutSubviews()
let inset: CGFloat = bounds.size.height/2
imageView.frame = CGRect(x: 0, y: 0, width: inset, height: inset)
imageView.center = CGPoint(x: inset, y: inset)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesBegan(touches, withEvent: event)
var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
let darkenFactor: CGFloat = 0.3
var darkerColor: UIColor
if originalBackgroundColor != nil && originalBackgroundColor!.getRed(&r, green: &g, blue: &b, alpha: &a){
darkerColor = UIColor(red: max(r - darkenFactor, 0), green: max(g - darkenFactor, 0), blue: max(b - darkenFactor, 0), alpha: a)
} else if originalBackgroundColor != nil && originalBackgroundColor!.getWhite(&r, alpha: &a){
darkerColor = UIColor(white: max(r - darkenFactor, 0), alpha: a)
} else{
darkerColor = UIColor.clearColor()
assert(false, "Item color should be RBG of White/Alpha in order to darken the button")
}
backgroundColor = darkerColor
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
super.touchesEnded(touches, withEvent: event)
backgroundColor = originalBackgroundColor
}
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
super.touchesCancelled(touches, withEvent: event)
backgroundColor = originalBackgroundColor
}
}
//MARK: Private Methods
private func animateSpringWithView(view: CalloutItem, idx: Int, initDelay: CGFloat){
let delay: NSTimeInterval = NSTimeInterval(initDelay) + NSTimeInterval(idx) * 0.1
UIView.animateWithDuration(0.5,
delay: delay,
usingSpringWithDamping: 10.0,
initialSpringVelocity: 50.0,
options: UIViewAnimationOptions.BeginFromCurrentState,
animations: {
view.layer.transform = CATransform3DIdentity
view.alpha = 1
},
completion: nil)
}
#objc private func handleTap(recognizer: UITapGestureRecognizer){
let location = recognizer.locationInView(view)
if !CGRectContainsPoint(contentView.frame, location){
dismissAnimated(true, completion: nil)
} else{
let tapIndex = indexOfTap(recognizer.locationInView(contentView))
if let tapIndex = tapIndex{
selectItemAtIndex(tapIndex)
}
}
}
private func layoutSubviews(){
let x = showFromRight ? parentViewController!.view.bounds.size.width - width : 0
contentView.frame = CGRect(x: x, y: 0, width: width, height: parentViewController!.view.bounds.size.height)
blurView.frame = contentView.frame
layoutItems()
}
private func layoutItems(){
let leftPadding: CGFloat = (width - itemSize.width) / 2
let topPadding: CGFloat = leftPadding
for (index, item) in itemViews.enumerate(){
let idx: CGFloat = adjustForNavigationBar ? CGFloat(index) + 0.5 : CGFloat(index)
let frame = CGRect(x: leftPadding, y: topPadding*idx + itemSize.height*idx + topPadding, width:itemSize.width, height: itemSize.height)
item.frame = frame
item.layer.cornerRadius = frame.size.width / 2
item.layer.borderColor = UIColor.clearColor().CGColor
item.alpha = 0
if selectedIndices.containsIndex(index){
if let borderColors = borderColors{
item.layer.borderColor = borderColors[index].CGColor
}
}
}
let itemCount = CGFloat(itemViews.count)
if adjustForNavigationBar{
contentView.contentSize = CGSizeMake(0, (itemCount + 0.5) * (itemSize.height + topPadding) + topPadding)
} else {
contentView.contentSize = CGSizeMake(0, itemCount * (itemSize.height + topPadding) + topPadding)
}
}
private func indexOfTap(location: CGPoint) -> Int? {
var index: Int?
for (idx, item) in itemViews.enumerate(){
if CGRectContainsPoint(item.frame, location){
index = idx
break
}
}
return index
}
private func addToParentViewController(viewController: UIViewController, callingAppearanceMethods: Bool){
if let _ = parentViewController{
removeFromParentViewControllerCallingAppearanceMethods(callingAppearanceMethods)
}
if callingAppearanceMethods{
beginAppearanceTransition(true, animated: false)
}
viewController.addChildViewController(self)
viewController.view.addSubview(view)
didMoveToParentViewController(self)
if callingAppearanceMethods{
endAppearanceTransition()
}
}
private func removeFromParentViewControllerCallingAppearanceMethods(callAppearanceMethods: Bool){
if callAppearanceMethods{
beginAppearanceTransition(false, animated: false)
}
willMoveToParentViewController(nil)
view.removeFromSuperview()
removeFromParentViewController()
if callAppearanceMethods{
endAppearanceTransition()
}
}
}
Instead of putting each button in it's own view, you need to create a view that contains 3 buttons and then add the circle to the view.

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