Cannot animate UIImage with timing delay in Swift - ios

I am attempting to animate the alpha of a UIImage after 3 seconds has passed. By default the alpha is set to 0 and after 3 seconds, the alpha should change to 1, thus displaying the image to the user. My code I wrote for my animation does set the alpha to 0, but I am unable to change the the alpha to 1 after 3 seconds. I am newer to swift and not sure where I am going wrong with this. Here is my code.
import UIKit
class WelcomeOneViewController: UIViewController {
#IBOutlet weak var swipeImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
swipeImageView.alpha = 0
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
displaySwipeView()
}
func displaySwipeView() {
UIView.animate(withDuration: 1.0, delay: 3.0, options: .beginFromCurrentState, animations: {
DispatchQueue.main.async { [weak self] in
self?.swipeImageView.alpha = 1
}
}, completion: nil)
}
}

Try it with this code:
import UIKit
class WelcomeOneViewController: UIViewController {
#IBOutlet weak var swipeImageView: UIImageView!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
swipeImageView.alpha = 0
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
displaySwipeView()
}
func displaySwipeView() {
swipeImageView.alpha = 0
UIView.animate(withDuration: 1.0, delay: 3.0, options: [], animations: {
self.swipeImageView.alpha = 1
}, completion: nil)
}
}

Try doing it on main queue like this:
func displaySwipeView() {
UIView.animate(withDuration: 1.0, delay: 3.0, options: .beginFromCurrentState, animations: {
DispatchQueue.main.async { [weak self] in
self?.swipeImageView.alpha = 1
}
}, completion: nil)
}
Hope it helps!!

Related

UIViewPropertyAnimator not respecting duration and delay parameters

I have a very strange issue in my app. I'm using UIViewPropertyAnimator to animate changing images inside UIImageView.
Sounds like trivial task but for some reaso my view ends up changing the image instantly so I end up with images flashing at light speed instead of given duration and delay parameters.
Here's the code:
private lazy var images: [UIImage?] = [
UIImage(named: "widgethint"),
UIImage(named: "shortcuthint"),
UIImage(named: "spotlighthint")
]
private var imageIndex = 0
private var animator: UIViewPropertyAnimator?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
animator = repeatingAnimator()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
animator?.stopAnimation(true)
}
private func repeatingAnimator() -> UIViewPropertyAnimator {
return .runningPropertyAnimator(withDuration: 2, delay: 6, options: [], animations: {
self.imageView.image = self.images[self.imageIndex]
}, completion: { pos in
if self.imageIndex >= self.images.count - 1 {
self.imageIndex = 0
} else {
self.imageIndex += 1
}
self.animator = self.repeatingAnimator()
})
}
So, according to the code animation should take 2 seconds to complete and start after 6 seconds delay, but it starts immediately and takes milliseconds to complete so I end up with horrible slideshow. Why could it be happening?
I also tried using the UIViewPropertyAnimator(duration: 2, curve: .linear) and calling repeatingAnimator().startAnimation(afterDelay: 6) but the result is the same.
Okay, I've figured it out, but it's still a bit annoying that such simple task cannot be done using the "Modern" animation API.
So, animating images inside UIImageView is apparently not supported by UIViewPropertyAnimator, during debugging I tried animating view's background color and it was working as expected. So I had to use Timer instead and old UIView.transition(with:) method
Here's the working code:
private let animationDelay: TimeInterval = 2.4
private var animationTimer: Timer!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
setAnimation(true)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
setAnimation(false)
}
private func setAnimation(_ enabled: Bool) {
guard enabled else {
animationTimer?.invalidate()
animationTimer = nil
return
}
animationTimer = Timer.scheduledTimer(withTimeInterval: animationDelay, repeats: true) { _ in
UIView.transition(with: self.imageView, duration: 0.5, options: [.curveLinear, .transitionCrossDissolve], animations: {
self.imageView.image = self.images[self.imageIndex]
}, completion: { succ in
guard succ else {
return
}
if self.imageIndex >= self.images.count - 1 {
self.imageIndex = 0
} else {
self.imageIndex += 1
}
})
}
}
Hopefully it will save someone some headaches in the future

(UILabel) nil, Cannot insert text in the label

Good day,
I seem to have such a simple problem but I just can not wrap my head around it.
I have a container view inside a view controller. In that container I have few labels. The container has its own view controller. In the view controller for the container I have a timer running and I want that label to show the timer. But every time I use that label the app crashes with
"Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value"
If I comment the line out that has this label then everything runs fine.
#IBOutlet weak var timeLabel: UILabel!
var counter = 0.0
var timer = Timer()
var isRunning = false
func startStopTimer () {
if isRunning {
timer.invalidate()
isRunning = false
}else {
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true)
isRunning = true
}
}
#objc func updateTimer() {
counter = counter + 0.1
timeLabel.text = String(counter)
}
This is the first time I play around with container view in the Main storyboard.
Anyone that knows what I am doing wrong or has suggestion what I can try to change?
Thanks
Jonas
Full Code
class MainViewController: UIViewController {
#IBOutlet weak var topContainer: UIView!
#IBOutlet weak var informationContainer: UIView!
#IBOutlet weak var startStopButtonOutlet: UIButton!
let informationContainerVC = InformationContainerViewController()
override func viewDidLoad() {
super.viewDidLoad()
setupView()
}
func setupView() {
topContainer.layer.cornerRadius = 5
topContainer.layer.masksToBounds = true
informationContainer.layer.cornerRadius = 5
informationContainer.layer.masksToBounds = true
startStopButtonOutlet.layer.cornerRadius = 5
startStopButtonOutlet.layer.masksToBounds = true
}
#IBAction func startStopButton_TouchUpInside(_ sender: UIButton) {
informationContainerVC.startStopTimer()
UIButton.animate(withDuration: 0.1, delay: 0.0, options: [.allowUserInteraction, .curveEaseOut], animations: {
sender.transform = CGAffineTransform.identity
}, completion: nil)
if informationContainerVC.isRunning {
startStopButtonOutlet.setTitle("Push to Pause", for: .normal)
}else {
startStopButtonOutlet.setTitle("Push to Start", for: .normal)
}
}
#IBAction func startStopButton_TouchDown(_ sender: UIButton) {
UIButton.animate(withDuration: 0.1, delay: 0.0, options: [.allowUserInteraction, .curveEaseIn], animations: {
sender.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
if self.informationContainerVC.isRunning {
sender.backgroundColor = UIColor.white.withAlphaComponent(0.5)
}else {
sender.backgroundColor = UIColor.green.withAlphaComponent(0.8)
}
}, completion: nil)
}
#IBAction func startStopButton_TouchUpOutside(_ sender: UIButton) {
UIButton.animate(withDuration: 0.1, delay: 0.0, options: [.allowUserInteraction, .curveEaseOut], animations: {
sender.transform = CGAffineTransform.identity
}, completion: nil)
}
}
Here is the code for the container view controller
class InformationContainerViewController: UIViewController {
#IBOutlet weak var timeLabel: UILabel!
var counter = 0.0
var timer = Timer()
var isRunning = false
func startStopTimer () {
if isRunning {
timer.invalidate()
isRunning = false
}else {
timer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true)
isRunning = true
}
}
#objc func updateTimer() {
counter = counter + 0.1
timeLabel.text = String(counter)
}
}
When you say let informationContainerVC = InformationContainerViewController() you are creating a new instance of InformationContainerViewController that is not linked to the storyboard, so none of the outlets are set.
You need to get a reference to the view controller instance that is actually in your container view. You can do this in prepare(for segue:); If you look at your storyboard you will see that there is an embed segue that links your containing view controller to the contained view controller.
In your MainViewController:
var informationContainerVC: InformationContainerViewController?
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destVC = segue.destination as? InformationContainerViewController {
self.informationContainerVC = destVC
}
}
#IBAction func startStopButton_TouchUpInside(_ sender: UIButton) {
informationContainerVC?.startStopTimer()
UIButton.animate(withDuration: 0.1, delay: 0.0, options: [.allowUserInteraction, .curveEaseOut], animations: {
sender.transform = CGAffineTransform.identity
}, completion: nil)
if informationContainerVC?.isRunning {
startStopButtonOutlet.setTitle("Push to Pause", for: .normal)
} else {
startStopButtonOutlet.setTitle("Push to Start", for: .normal)
}
}
Now you will have a reference to the correct view controller instance

UIViewControllerAnimatedTransitioning: screen keeps getting black under custom view

I have found many related topics on SO (and elsewhere) but still couldn't find a solution to my problem. I want to display a custom alert on my view using UIViewControllerTransitioningDelegate. So first, in my initial view controller, here is the call:
#IBAction func tappedButton(_ sender: Any) {
MyAlertViewController.presentIn(viewController: self)
}
And here the code of MyAlertViewController:
import UIKit
open class MyAlertViewController: UIViewController, UIViewControllerTransitioningDelegate {
#IBOutlet weak var overlayView: UIView?
#IBOutlet weak var alertView: UIView?
#IBOutlet weak var alertCenterYConstraint: NSLayoutConstraint?
public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: "MyAlertView", bundle: nil)
self.transitioningDelegate = self
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
static func presentIn(viewController: UIViewController) {
let alertViewController = MyAlertViewController()
if Thread.isMainThread {
viewController.present(alertViewController, animated: true, completion: nil)
} else {
DispatchQueue.main.async {
viewController.present(alertViewController, animated: true, completion: nil)
}
}
}
public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return MyDismissAlertViewAnimationController()
}
public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return MyPresentAlertViewAnimationController()
}
}
class MyPresentAlertViewAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.3
}
public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let toViewController: MyAlertViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) as! MyAlertViewController
let duration = self.transitionDuration(using: transitionContext)
let containerView = transitionContext.containerView
toViewController.view.frame = containerView.frame
containerView.addSubview(toViewController.view)
toViewController.overlayView?.alpha = 0.0
UIView.animate(withDuration: duration, animations: {
toViewController.overlayView?.alpha = 0.6
})
let finishFrame = toViewController.alertView?.frame
var startingFrame = finishFrame
startingFrame?.origin.y = -((finishFrame?.height)!)
toViewController.alertView?.frame = startingFrame!
UIView.animate(withDuration: 0.5, delay: 0.0, usingSpringWithDamping: 0.7, initialSpringVelocity: 1.0, options: .layoutSubviews, animations: {
toViewController.alertView?.frame = finishFrame!
}, completion: { result in
transitionContext.completeTransition(result)
})
}
}
class MyDismissAlertViewAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.3
}
public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let fromViewController: MyAlertViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) as! MyAlertViewController
let duration = self.transitionDuration(using: transitionContext)
UIView.animate(withDuration: duration, animations: {
fromViewController.overlayView?.alpha = 0.0
})
var finishFrame = fromViewController.alertView?.frame
finishFrame?.origin.y = -(finishFrame?.height)!
finishFrame?.origin.y = fromViewController.isDismissingByBottom ? fromViewController.view.frame.size.height : -(finishFrame?.height)!
UIView.animate(withDuration: duration, delay: 0.0, usingSpringWithDamping: 1.0, initialSpringVelocity: 1.0, options: .layoutSubviews, animations: {
fromViewController.alertView?.frame = finishFrame!
}, completion: { result in
transitionContext.completeTransition(true)
})
}
}
The animation works fine, the black screen only appears after the call to completeTransition() as you can see below:
Thanks for your help...
I think you need to either set your background color like so:
self.view.backgroundColor = .clear
This will make sure the background you see is not just the background color of your modal.
Or prevent the presenting view controller to be removed from the screen by making the modal presentation style overCurrentContext
self.modalPresentationStyle = .overCurrentContext

Why are the animated UI Labels in my code starting from within the view?

I'm a very new-to-code learner. I've been taking some online courses, and came across this project recently.
I basically copied the code as I saw it on the screen, as the project files weren't available to download.
The animation is supposed to bring the UILabels from outside the view and position them within the view.
What seems to happen however, is the labels are starting within the view screen and animate outward and beyond.
I've copied the project below. Any help is so very appreciated.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var helloWorld: UILabel!
#IBOutlet weak var secondLabel: UILabel!
#IBOutlet weak var hiddenLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
helloWorld.center.y -= view.bounds.height
secondLabel.center.y += view.bounds.height
hiddenLabel.alpha = 0.0
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
// Animate Hello World label
UIView.animateWithDuration(1.5, animations: {
self.helloWorld.center.y += self.view.bounds.height
}, completion: { finished in
self.secondAnimation()
})
// Animate background color change
UIView.animateWithDuration(2.0, delay: 1.5, options: [], animations: {
self.view.backgroundColor = UIColor.yellowColor()
}, completion:nil)
}
// Animate second Label
func secondAnimation() {
UIView.animateWithDuration(1.5, delay: 0.0, options: [], animations: { () -> Void in
self.secondLabel.center.y -= self.view.bounds.height
}, completion:nil)
}
func backgroundColor() {
UIView.animateWithDuration(2.5, animations: {
self.view.backgroundColor = UIColor.blackColor()
}, completion: nil)
UIView.animateWithDuration(1.0, delay: 1.5, options: [], animations: {
self.hiddenLabel.alpha = 1.0
}, completion:nil)
}
}
I would check hello world.center.y value in viewWillAppear, before you subtract the view.bounds.height. If center.y value is large and positive, then the subtraction might be setting the center.y value to be inside of the view. If this is the case, then you would want to change the subtraction in viewWillAppear to addition and then the addition in the viewDidAppear animation to subtraction.

Unable to Get UIButton to Fade In on ViewDidLoad

As stated in the title, I am unable to get the UIButton to fade in on the ViewDidLoad method. Here is my code thus far:
ViewController.swift
import UIKit
import QuartzCore
class ViewController: UIViewController {
#IBOutlet weak var nextButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
self.nextButton.fadeIn(duration: 10.0, delay: 10.0)
}
}
UIViewExtensions.swift
import Foundation
import UIKit
extension UIView {
func fadeIn(duration: NSTimeInterval = 1.0, delay: NSTimeInterval = 0.0, completion: ((Bool) -> Void) = {(finished: Bool) -> Void in}) {
UIView.animateWithDuration(duration, delay: delay, options: UIViewAnimationOptions.CurveEaseIn, animations: {
self.alpha = 1.0
}, completion: completion) }
func fadeOut(duration: NSTimeInterval = 1.0, delay: NSTimeInterval = 0.0, completion: (Bool) -> Void = {(finished: Bool) -> Void in}) {
UIView.animateWithDuration(duration, delay: delay, options: UIViewAnimationOptions.CurveEaseIn, animations: {
self.alpha = 0.0
}, completion: completion)
}
}
You do not want to start any animations like a fade in, on the viewDidLoad. This method is called when the class is finished initializing (right after the init). It happens before the view is visible. You want to start animations in the viewDidAppear. This is called once the view is visible on the screen. When you start it in the viewDidLoad, it's already done the animation by the time it gets to the viewDidAppear, assuming of course you did the fade in over about .5 second.
Set you button alpha to 0 on storyboard,so that you can fade in.
Because,if your button alpha is 1,then you want to animate to 1,IOS do nothing
override func viewDidAppear(animated: Bool) {
self.button.fadeIn(duration: 3, delay: 0)
}

Resources