问题
I have a fairly simple set up in my main storyboard:
- A stack view which includes three views
- The first view has a fixed height and contains a segment controller
- The other two views have no restrictions, the idea being that only one will be active at a time and thus fill the space available
I have code that will deal with the changing view active views as follows:
import Foundation
import UIKit
class ViewController : UIViewController {
@IBOutlet weak var stackView: UIStackView!
@IBOutlet weak var segmentController: UISegmentedControl!
@IBAction func SegmentClicked(_ sender: AnyObject) {
updateView(segment: sender.titleForSegment(at: sender.selectedSegmentIndex)!)
}
override func viewDidLoad() {
updateView(segment: "First")
}
func updateView(segment: String) {
UIView.animate(withDuration: 1) {
if(segment == "First") {
self.stackView.arrangedSubviews[1].isHidden = false
self.stackView.arrangedSubviews[2].isHidden = true
} else {
self.stackView.arrangedSubviews[1].isHidden = true
self.stackView.arrangedSubviews[2].isHidden = false
}
print("Updating views")
print("View 1 is \(self.stackView.arrangedSubviews[1].isHidden ? "hidden" : "visible")")
print("View 2 is \(self.stackView.arrangedSubviews[2].isHidden ? "hidden" : "visible")")
}
}
}
As you can see, when the tab called 'First' is selected, the subview at index 1 should show, whilst 2 is hidden, and when anything else is selected, the subview at index 2 should show, whilst 1 is hidden.
This appears to work at first, if I go slowly changing views, but if I go a bit quicker, the view at index 1 seems to remain permanently hidden after a few clicks, resulting in the view at index 0 covering the whole screen. I've placed an animation showing the issue and a screenshot of the storyboard below. The output shows that when the problem happens, both views remain hidden when clicking on the first segment.
Can anybody tell me why this is happening? Is this a bug, or am I not doing something I should be?
Many thanks in advance!
Update: I seem to be able to reliably reproduce the issue by going to the First > Second > Third > Second > First segments in that order.
回答1:
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. If you show it three times, you need to hide it three times to actually hide it (assuming it was hidden to start).
This is independent of using animation.
So if you do something like this in your code, only hiding a view if it's visible, you'll avoid this problem:
if myView.isHidden == false {
myView.isHidden = true
}
回答2:
Building on the nice answer by Dave Batton, you can also add a UIView extension to make the call site a bit cleaner, IMO.
extension UIView {
var isHiddenInStackView: Bool {
get {
return isHidden
}
set {
if isHidden != newValue {
isHidden = newValue
}
}
}
}
Then you can call stackView.subviews[someIndex].isHiddenInStackView = false
which is helpful if you have multiple views to manage within your stack view versus a bunch of if statements.
回答3:
In the end, after trying all the suggestions here I still couldn't work out why it was behaving like this so I got in touch with Apple who asked me to file a bug report. I did however find a work around, by unhiding both views first, which solved my problem:
func updateView(segment: String) {
UIView.animate(withDuration: 1) {
self.stackView.arrangedSubviews[1].isHidden = false
self.stackView.arrangedSubviews[2].isHidden = false
if(segment == "First") {
self.stackView.arrangedSubviews[2].isHidden = true
} else {
self.stackView.arrangedSubviews[1].isHidden = true
}
}
}
回答4:
Based on what I can see, this weird behavior is caused by the animation duration. As you can see, it takes one second for the animation to complete, but if you start switching the segmentControl faster than that, then I would argue that is what is causing this behavior.
What you should do is deactivate the user interactivity when the method is called, and then re-enable it once the animation is complete.
It should look something like this:
func updateView(segment: String) {
segmentControl.userInteractionEnabled = false
UIView.animateWithDuration(1.0, animations: {
if(segment == "First") {
self.stackView.arrangedSubviews[1].isHidden = false
self.stackView.arrangedSubviews[2].isHidden = true
} else {
self.stackView.arrangedSubviews[1].isHidden = true
self.stackView.arrangedSubviews[2].isHidden = false
}
print("Updating views")
print("View 1 is \(self.stackView.arrangedSubviews[1].isHidden ? "hidden" : "visible")")
print("View 2 is \(self.stackView.arrangedSubviews[2].isHidden ? "hidden" : "visible")")
}, completion: {(finished: Bool) in
segmentControl.userInteractionEnabled = true
}
}
While this will prevent from fast switching (which you may see as a downside), the only other way I am aware of that solve this is by removing the animations altogether.
回答5:
Check the configuration and autolayout constraints on the stack view and the subviews, particularly the segmented control.
The segmented control complicates the setup for the stack view, so I'd take the segmented control out of the stack view and set its constraints relative to the main view.
With the segmented control out of the stack view, it's relatively straightforward to set up the stack view so that your code will work properly.
Reset the constraints on the stack view so that it is positioned below the segmented control and covers the rest of the superview. In the Attributes Inspector, set Alignment to Fill, Distribution to Fill Equally, and Content Mode to Scale to Fill.
Remove the constraints on the subviews and set their Content Mode to Scale to Fill.
Adjust the indexing on arrangedSubviews in your code and it should work automagically.
来源:https://stackoverflow.com/questions/40001416/swift-disappearing-views-from-a-stackview