StackView isHidden attribute not updating as expected
Asked Answered
O

4

28

I'm trying to update a UIStackView so that a field displays, should the value of a UITextField equal "Other". Here is my code:

@IBOutlet var stackView: UIStackView!
func updateView() {
    print("UPDATING")
    UIView.animate(withDuration: 0.25, animations: { () -> Void in
         if(self.myTextField.text! == "Other") {
              print("SHOWING")
              self.stackView.arrangedSubviews[3].isHidden = false
         } else {
              print("HIDING")
              self.stackView.arrangedSubviews[3].isHidden = true
         }
         print("Is hidden: \(self.stackView.arrangedSubviews[3].isHidden )")
    })

An example output looks like this:

> UPDATING
> HIDING
> Is hidden: true
> UPDATING
> SHOWING
> Is hidden: true

As you can see, the isHidden attribute is reported as true, regardless of what the code above has set it to. I can't really figure out why that might be, but perhaps someone on here can? Is there something obvious to check? Is there any reason why isHidden can't be updated? (note there are no errors appearing in the output).

Original answered 7/5, 2017 at 12:51 Comment(5)
If I take your code sample plus your description literally it doesn’t work because of case sensitivity—“other” (as in your description) vs. “Other” (as in the sample. You might want to consider this: self.myTextField.text!.lowercaseString == "other".Tosch
Sorry, that's just a bad description, I'll update - see the output for proof thereOriginal
@Original did you figure it out? zisoft's solution removes the animation, so it is not good for me. If I remove the animation block, everything works as expected anyways.Saenz
@Saenz I believe zisoft's answer did allow for animations (it certainly seems to looking at the code), however I don't actually have a working copy to prove it at this moment - sorry!Original
No problem, I used a workaround where I’m setting the items to shown/hidden also in the animation’s completion block, not just in the animation block I don’t like the solution, but it fixed the bugSaenz
G
7

Updates on the user interface always have to be done on the main thread (THE LAW).

So wrap you UI updates on the main thead:

@IBOutlet var stackView: UIStackView!
func updateView() {
    print("UPDATING")
    UIView.animate(withDuration: 0.25, animations: { () -> Void in
        DispatchQueue.main.async {  // UI updates on the main thread
            if(self.myTextField.text! == "Other") {
                print("SHOWING")
                self.stackView.arrangedSubviews[3].isHidden = false
             } else {
                print("HIDING")
                self.stackView.arrangedSubviews[3].isHidden = true
             }
             print("Is hidden: \(self.stackView.arrangedSubviews[3].isHidden )")
        }
    })
Gunman answered 7/5, 2017 at 12:58 Comment(4)
Worked perfectly, thank you! Do you know why UIView.animate is not done on the main thread by default? How else would one use it?Original
Glad to help you. Please mark the answer as accepted. Animations are running on another thread to prevent the main thread from getting blocked.Gunman
I can't until the timer has expired, but I of course will do so. I think that makes sense - so you can't change an isHidden property on anything but the main thread, but once those are changed, then the animation can run away from it. Thanks again!Original
I don't think it's the right solution, the duration of animation is ignored on the async. If you replace your 0.25sec to 3sec, you'll see that it doesn't work.Original
B
61

It's a known UIStackView bug (http://www.openradar.me/25087688). There is a thread on SO about it: (Swift: Disappearing views from a stackView). Long story short:

The bug is that hiding and showing views in a stack view is cumulative. Weird Apple bug. If you hide a view in a stack view twice, you need to show it twice to get it back.

To fix this issue you can use the following extension:

extension UIView {
    var isHiddenInStackView: Bool {
        get {
            return isHidden
        }
        set {
            if isHidden != newValue {
                isHidden = newValue
            }
        }
    }
}
Bentinck answered 14/3, 2019 at 11:37 Comment(1)
I'm still seeing this issue in iOS 13. I have two labels in a StackView in a UITableViewCell and one or both of the labels should be hidden. Sometimes the labels were visible even though they were marked as hidden. I was only able to fix it by setting the labels to Hidden true in the storyboard and then using isHiddednInStackView to update their visibility. If they started out isHidden false and then had isHidden set to true they still were sometimes visible. Rotating the iPad could make labels in some rows become visible.Uglify
G
7

Updates on the user interface always have to be done on the main thread (THE LAW).

So wrap you UI updates on the main thead:

@IBOutlet var stackView: UIStackView!
func updateView() {
    print("UPDATING")
    UIView.animate(withDuration: 0.25, animations: { () -> Void in
        DispatchQueue.main.async {  // UI updates on the main thread
            if(self.myTextField.text! == "Other") {
                print("SHOWING")
                self.stackView.arrangedSubviews[3].isHidden = false
             } else {
                print("HIDING")
                self.stackView.arrangedSubviews[3].isHidden = true
             }
             print("Is hidden: \(self.stackView.arrangedSubviews[3].isHidden )")
        }
    })
Gunman answered 7/5, 2017 at 12:58 Comment(4)
Worked perfectly, thank you! Do you know why UIView.animate is not done on the main thread by default? How else would one use it?Original
Glad to help you. Please mark the answer as accepted. Animations are running on another thread to prevent the main thread from getting blocked.Gunman
I can't until the timer has expired, but I of course will do so. I think that makes sense - so you can't change an isHidden property on anything but the main thread, but once those are changed, then the animation can run away from it. Thanks again!Original
I don't think it's the right solution, the duration of animation is ignored on the async. If you replace your 0.25sec to 3sec, you'll see that it doesn't work.Original
F
1

This is still an active bug for iOS 16. So @Timur Bernikovich's solution is the only working solution. If you do not like to add any extension you need to check isHidden before the update. It does not help to run on the main thread since it will mess up the current animation (if you applied any animation for UIStackView). Therefore it's better to check the updating value before updating the view.isHidden value. As an example:

   var stackView: UIStackView!

    func isSectionActive(_ isActive: Bool) {
      UIView.animate(withDuration: 0.3) { [weak self] in
          self?.stackView.arrangedSubviews.forEach {
              // Active bug on UIStackView 👉🏻 http://www.openradar.me/25087688
              // So do not hide a subview twice
              if $0.isHidden != !isActive {
                  $0.isHidden = !isActive
                  $0.alpha = isActive ? 1.0 : 0.0
              }
          }
          self?.stackView.layoutIfNeeded()
      }
    }
Farica answered 24/5, 2023 at 9:46 Comment(0)
I
0

try to manipulate alpha along with isHidden property:

self.stackView.arrangedSubviews[3].isHidden = true
self.stackView.arrangedSubviews[3].alpha = 0

self.stackView.arrangedSubviews[3].isHidden = false
self.stackView.arrangedSubviews[3].alpha = 1
Inhume answered 7/5, 2017 at 13:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.