0
votes

I have looked and tried every solution I could find online as to why my animations is not properly firing. DISCLOSURE: they work fine when put in viewDidAppear(). This question has been asked countless times but none of the solutions work. The results of the animations appear but not with the delay specified, they are instant. What I would like is to fade in a my requestRideButton in and out using either isHidden with UIView.transition or alpha with UIView.animate.

I would also like to move the button while it is fading. I have tried every combination of self.view.layoutIfNeeded() both in and out the animate closure with the constraint in and out. I have also tried it with self.view.superview?.layoutIfNeeded()

class RideRequestViewController: UIViewController, MKMapViewDelegate {

@IBOutlet weak var rideRequestBottomButtonConstraint: NSLayoutConstraint!
@IBOutlet weak var rideRequestButtonTopConstraint: NSLayoutConstraint!

// Views
@IBOutlet var mapView: MKMapView!

@IBOutlet var rideDetailsView: UIView!

@IBOutlet var requestRideButton: UIButton!

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    // Layout map view to fill screen
    mapView.frame = view.bounds

    // Apply corner radius and shadow styling to floating views
    let cornerRadius: CGFloat = 5.0

    inputContainerView.layoutCornerRadiusAndShadow(cornerRadius: cornerRadius)
    originButton.layoutCornerRadiusMask(corners: [.topLeft, .topRight], cornerRadius: cornerRadius)
    paymentButton.layoutCornerRadiusMask(corners: .bottomLeft, cornerRadius: cornerRadius)
    priceButton.layoutCornerRadiusMask(corners: .bottomRight, cornerRadius: cornerRadius)

    rideDetailsView.layoutCornerRadiusAndShadow(cornerRadius: cornerRadius)
    pilotView.layoutCornerRadiusMask(corners: [.topLeft, .bottomLeft], cornerRadius: cornerRadius)
    vehicleView.layoutCornerRadiusMask(corners: [.topRight, .bottomRight], cornerRadius: cornerRadius)

    requestRideButton.layoutCornerRadiusAndShadow(cornerRadius: cornerRadius)

}

override func viewDidLoad() {
    ref = Database.database().reference()
}

func animate() {
    self.rideRequestButtonTopConstraint.constant = -44
    UIView.animate(withDuration: 1) {
        self.view.layoutIfNeeded()
    }
}

@IBAction
private func handleRequestRideButtonTapped() {
    switch rideRequestState {
    case .none:
        // Update to requesting state
        rideRequestState = .requesting
        // Perform payment request
        paymentContext.requestPayment()
    case .requesting:
        // Do nothing
        // Show button
        break
    case .active:
        // Complete the ride
        completeActiveRide()
    }
}

private func reloadRequestRideButton() {
    guard originPlacemark != nil && destinationPlacemark != nil && paymentContext.selectedPaymentMethod != nil else {
        // Show disabled state
        requestRideButton.backgroundColor = .riderGrayColor
        requestRideButton.setTitle("CONFIRM DELIVERY", for: .normal)
        requestRideButton.setTitleColor(.black, for: .normal)
        requestRideButton.setImage(nil, for: .normal)
        requestRideButton.isEnabled = false
        return
    }

    animate() // <--- view just disappears instantly instead
    switch rideRequestState {
    case .none:
        // Show enabled state
        requestRideButton.backgroundColor = .riderYellowColor
        requestRideButton.setTitle("CONFIRM DELIVERY", for: .normal)
        requestRideButton.setTitleColor(.black, for: .normal)
        requestRideButton.setImage(nil, for: .normal)
        requestRideButton.isEnabled = true
    case .requesting:
        // Show loading state
        requestRideButton.backgroundColor = .riderYellowColor
        requestRideButton.setTitle("...", for: .normal)
        requestRideButton.setTitleColor(.white, for: .normal)
        requestRideButton.setImage(nil, for: .normal)
        requestRideButton.isEnabled = false
    case .active:
        // Show completion state
        requestRideButton.backgroundColor = .white
        requestRideButton.setTitle("Complete Ride", for: .normal)
        requestRideButton.setTitleColor(.riderDarkBlueColor, for: .normal)
        requestRideButton.setImage(nil, for: .normal)
        requestRideButton.isEnabled = true
    }
}
1
from where does this method is being called "reloadRequestRideButton"ankit
Possible duplicate: stackoverflow.com/questions/12622424/…. Also part of one of the answers (@ankit): The following is a Twitter conversation between Martin Pilkington who teaches iOS, and Ken Ferry who wrote Auto Layout. Ken explains that though changing constants outside of the animation block may currently work, it's not safe and they should really be change inside the animation block. twitter.com/kongtomorrow/status/440627401018466305Scriptable
@ankit the full code would have been long but it's called in varius places like: private var pickupPlacemark: MKPlacemark? { didSet { reloadRequestRideButton() } }Alexyesiam
@Scriptable I have used layoutIfNeeded() within the block and yet it does not workAlexyesiam
@Scriptable, how can animation be triggered if we don't write layoutIfNeeded in block and from WWDC, there they adjusted constant outside block and just did layout inside .ankit

1 Answers

0
votes

Changes to views to be animated should be inside the animate block.

func animate() {
    UIView.animate(withDuration: 1) {
        self.rideRequestButtonTopConstraint.constant = -44
        self.view.layoutIfNeeded()
    }
}

Changing the constraint in the line above will just update a value and move the view. It won't animate it

UPDATE

So based on looking at another answer and some additional research, The author of UIKit suggests that we update constraints and call layoutIfNeeded inside the animation block like above.

Here is the playground I used to test it

import UIKit
import PlaygroundSupport

class ViewController: UIViewController {

    let movableView = UIView()
    var topConstraint: NSLayoutConstraint?

    override func viewDidLoad() {
        super.viewDidLoad()

        let button = UIButton(frame: CGRect(x: 0, y: 0, width: 50, height: 50))
        view.addSubview(button)
        button.setTitle("Animate", for: .normal)
        button.addTarget(self, action: #selector(animate), for: .touchUpInside)

        view.addSubview(movableView)

        movableView.backgroundColor = .red
        movableView.translatesAutoresizingMaskIntoConstraints = false
        topConstraint = NSLayoutConstraint(item: movableView, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1.0, constant: 100)
        topConstraint?.isActive = true

        NSLayoutConstraint(item: movableView, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1.0, constant: 0).isActive = true
        NSLayoutConstraint(item: movableView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 100).isActive = true
        NSLayoutConstraint(item: movableView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 100).isActive = true
    }

    @objc func animate() {

        UIView.animate(withDuration: 4.0, animations: {
            self.topConstraint?.constant = -100
            self.view.layoutIfNeeded()
        })
    }
}


let vc = ViewController()

PlaygroundPage.current.liveView = vc.view