0

I have two views that are part of a login process. On the top of the view I have a progress bar, which is animated to fill to the progress the new view represents.

Both Views are built the same way.

All my contents are inside a UIView contentView, which have the following constraints:

            contentView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor),
        contentView.widthAnchor.constraint(equalTo: self.view.widthAnchor, multiplier: 0.9),
        contentView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),

ofcourse, all constraints are activated inside viewDidLoad. Inside viewWillAppear I calulate the progress, that the progressbar should display before the change loads and update the constraints:

        progressFillConstraint = progressFill.widthAnchor.constraint(equalTo: progressBackground.widthAnchor, multiplier: progress)
    progressFillConstraint.isActive = true
    newProgressFillConstraint = progressFill.widthAnchor.constraint(equalTo: progressBackground.widthAnchor, multiplier: (currentPage/totalPages))
    newProgressFillConstraint.isActive = false
    self.view.layoutIfNeeded()

Inside ViewDidAppear I then calculate the new progress and animate the change:

        defaults.set(currentPage, forKey: "signupProcessProgress")

    progressFillConstraint.isActive = false
    newProgressFillConstraint.isActive = true
    let animationDuration = 2.0
    UIView.animate(withDuration: animationDuration) {
        self.view.layoutIfNeeded()
    }

And here lies the problem. For some wierd reason, it works very well exept one thing. The View loads as if I set the top constraint of the contentview to view.topAnchor and not to view.safeAreaLayoutGuide.topAnchor and animates as I call self.view.layoutIfNeeded() inside my progress Animation from viewDidAppear. So the View starts further top and animates down. I don't understand why since I never change the top constraint of the contentView. To me it seems as if the safeAreaLayoutGuide changes between viewWillAppear and viewDidAppear.

What am I missing here?

Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116
Jan L
  • 233
  • 1
  • 10
  • 1
    I think safe layout guides are zero until the view is on screen. They will be zero in `viewWillAppear`. You might be able to get it to work by triggering a layout in `viewDidAppear` *before* making any other constraint changes and then using your existing code to change constraints and animate another layout. – Geoff Hackworth Mar 15 '23 at 14:56
  • Thanks @GeoffHackworth, that really was the issue. Didn't know the safe Area is only set after the view loaded. – Jan L Mar 16 '23 at 10:39
  • "If the view is not currently installed in a view hierarchy, or is not yet visible onscreen, the layout guide edges are equal to the edges of the view." https://developer.apple.com/documentation/uikit/uiview/2891102-safearealayoutguide – Geoff Hackworth Mar 16 '23 at 10:44

1 Answers1

0

It doesn't look like there's anything wrong... of course, we can't see how you're setting up the rest of your constraints.

Here is a quick example - maybe if you compare this to your code, you'll find a difference:

class JanProgressVC: UIViewController {
    
    let contentView = UIView()
    let progressBackground = UIView()
    let progressFill = UIView()
    
    var progressFillConstraint: NSLayoutConstraint!
    
    var currentPage: Int = 1
    let totalPages: Int = 3
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = UIColor(white: 0.95, alpha: 1.0)

        contentView.backgroundColor = .lightGray
        progressBackground.backgroundColor = .red
        progressFill.backgroundColor = .green
        
        [contentView, progressBackground, progressFill].forEach { v in
            v.translatesAutoresizingMaskIntoConstraints = false
        }
        
        contentView.addSubview(progressBackground)
        contentView.addSubview(progressFill)
        
        view.addSubview(contentView)
        
        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            
            contentView.topAnchor.constraint(equalTo: g.topAnchor),
            contentView.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.9),
            contentView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            contentView.heightAnchor.constraint(equalToConstant: 40.0),

            progressBackground.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 4.0),
            progressBackground.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 0.0),
            progressBackground.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: 0.0),
            progressBackground.heightAnchor.constraint(equalToConstant: 12.0),

            progressFill.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 0.0),
            progressFill.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -4.0),
            progressFill.heightAnchor.constraint(equalToConstant: 12.0),
            
        ])

        let progress: CGFloat = CGFloat(currentPage) / CGFloat(totalPages)
        progressFillConstraint = progressFill.widthAnchor.constraint(equalTo: progressBackground.widthAnchor, multiplier: progress)
        progressFillConstraint.isActive = true
        
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        // increment currentPage
        currentPage += 1
        
        let progress: CGFloat = CGFloat(currentPage) / CGFloat(totalPages)

        progressFillConstraint.isActive = false
        progressFillConstraint = progressFill.widthAnchor.constraint(equalTo: progressBackground.widthAnchor, multiplier: progress)
        progressFillConstraint.isActive = true

        UIView.animate(withDuration: 2.0, animations: {
            self.view.layoutIfNeeded()
        })
    }
    
}

When this loads, it will look like this (page 1 of 3, or 33% progress):

enter image description here

and will smoothly animate to this (page 2 of 3, or 67% progress):

enter image description here

As a side note... this doesn't affect anything, but there is no need to move the initial "progress fill" constraint setup to viewWillAppear().

DonMag
  • 69,424
  • 5
  • 50
  • 86
  • Thanks for your effort! Getting the animation to work was not the issue, however, I learned that I could override existing constraints. Geoff's comment solved my issue. – Jan L Mar 16 '23 at 10:38