Swift:从 stackView 中消失的视图
Swift: Disappearing views from a stackView
我的主故事板中的设置相当简单:
- 包含三个视图的堆栈视图
- 第一个视图具有固定高度并包含一个段控制器
- 其他两个视图没有限制,想法是一次只有一个视图处于活动状态,从而填充 space 个可用
我有如下代码可以处理不断变化的视图活动视图:
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,而 1 是隐藏的。
如果我慢慢地改变视图,这似乎一开始是有效的,但如果我改变得更快一点,索引 1 处的视图似乎在点击几下后永久隐藏,导致索引 0 处的视图覆盖整个屏幕。我已经放置了一个显示问题的动画和下面故事板的屏幕截图。输出显示,当问题发生时,单击第一段时两个视图都保持隐藏状态。
谁能告诉我为什么会这样?这是一个错误,还是我没有做我应该做的事情?
非常感谢!
更新:我似乎能够通过按顺序转到第一 > 第二 > 第三 > 第二 > 第一段来可靠地重现问题。
据我所知,这种奇怪的行为是由动画持续时间引起的。如您所见,动画完成需要一秒钟,但如果您开始切换 segmentControl 的速度比这快,那么我认为这就是导致此行为的原因。
您应该做的是在调用该方法时停用用户交互性,然后在动画完成后重新启用它。
它应该看起来像这样:
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
}
}
虽然这会阻止快速切换(您可能会认为这是一个缺点),但我知道解决此问题的唯一其他方法是完全删除动画。
检查堆栈视图和子视图的配置和自动布局约束,尤其是分段控件。
分段控件使堆栈视图的设置变得复杂,因此我将分段控件从堆栈视图中取出并设置其相对于主视图的约束。
由于堆栈视图中的分段控件,设置堆栈视图相对简单,这样您的代码才能正常工作。
重置堆栈视图的约束,使其位于分段控件下方并覆盖父视图的其余部分。在 Attributes Inspector 中,将 Alignment 设置为 Fill,将 Distribution 设置为 Fill Equally,将 Content Mode 设置为 Scale to Fill。
移除对子视图的约束并将其内容模式设置为缩放以填充。
在您的代码中调整 arrangedSubviews 的索引,它应该会自动运行。
最后,在尝试了这里的所有建议后,我仍然无法弄清楚为什么会这样,所以我联系了 Apple,后者让我提交错误报告。但是我确实找到了解决方法,首先取消隐藏两个视图,这解决了我的问题:
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
}
}
}
错误是在堆栈视图中隐藏和显示视图是累积的。奇怪的苹果错误。如果在堆栈视图中隐藏视图两次,则需要显示两次才能将其取回。如果显示三遍,则需要隐藏三遍才能真正隐藏它(假设它隐藏开始)。
这与使用动画无关。
因此,如果您在代码中执行类似的操作,仅隐藏可见的视图,您将避免此问题:
if !myView.isHidden {
myView.isHidden = true
}
在 Dave Batton 的精彩回答的基础上,您还可以添加 UIView 扩展以使调用站点更简洁,IMO。
extension UIView {
var isHiddenInStackView: Bool {
get {
return isHidden
}
set {
if isHidden != newValue {
isHidden = newValue
}
}
}
}
然后你可以调用 stackView.subviews[someIndex].isHiddenInStackView = false
如果你在堆栈视图中有多个视图要管理而不是一堆 if 语句,这将很有帮助。
我的主故事板中的设置相当简单:
- 包含三个视图的堆栈视图
- 第一个视图具有固定高度并包含一个段控制器
- 其他两个视图没有限制,想法是一次只有一个视图处于活动状态,从而填充 space 个可用
我有如下代码可以处理不断变化的视图活动视图:
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,而 1 是隐藏的。
如果我慢慢地改变视图,这似乎一开始是有效的,但如果我改变得更快一点,索引 1 处的视图似乎在点击几下后永久隐藏,导致索引 0 处的视图覆盖整个屏幕。我已经放置了一个显示问题的动画和下面故事板的屏幕截图。输出显示,当问题发生时,单击第一段时两个视图都保持隐藏状态。
谁能告诉我为什么会这样?这是一个错误,还是我没有做我应该做的事情?
非常感谢!
更新:我似乎能够通过按顺序转到第一 > 第二 > 第三 > 第二 > 第一段来可靠地重现问题。
据我所知,这种奇怪的行为是由动画持续时间引起的。如您所见,动画完成需要一秒钟,但如果您开始切换 segmentControl 的速度比这快,那么我认为这就是导致此行为的原因。
您应该做的是在调用该方法时停用用户交互性,然后在动画完成后重新启用它。
它应该看起来像这样:
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
}
}
虽然这会阻止快速切换(您可能会认为这是一个缺点),但我知道解决此问题的唯一其他方法是完全删除动画。
检查堆栈视图和子视图的配置和自动布局约束,尤其是分段控件。
分段控件使堆栈视图的设置变得复杂,因此我将分段控件从堆栈视图中取出并设置其相对于主视图的约束。
由于堆栈视图中的分段控件,设置堆栈视图相对简单,这样您的代码才能正常工作。
重置堆栈视图的约束,使其位于分段控件下方并覆盖父视图的其余部分。在 Attributes Inspector 中,将 Alignment 设置为 Fill,将 Distribution 设置为 Fill Equally,将 Content Mode 设置为 Scale to Fill。
移除对子视图的约束并将其内容模式设置为缩放以填充。
在您的代码中调整 arrangedSubviews 的索引,它应该会自动运行。
最后,在尝试了这里的所有建议后,我仍然无法弄清楚为什么会这样,所以我联系了 Apple,后者让我提交错误报告。但是我确实找到了解决方法,首先取消隐藏两个视图,这解决了我的问题:
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
}
}
}
错误是在堆栈视图中隐藏和显示视图是累积的。奇怪的苹果错误。如果在堆栈视图中隐藏视图两次,则需要显示两次才能将其取回。如果显示三遍,则需要隐藏三遍才能真正隐藏它(假设它隐藏开始)。
这与使用动画无关。
因此,如果您在代码中执行类似的操作,仅隐藏可见的视图,您将避免此问题:
if !myView.isHidden {
myView.isHidden = true
}
在 Dave Batton 的精彩回答的基础上,您还可以添加 UIView 扩展以使调用站点更简洁,IMO。
extension UIView {
var isHiddenInStackView: Bool {
get {
return isHidden
}
set {
if isHidden != newValue {
isHidden = newValue
}
}
}
}
然后你可以调用 stackView.subviews[someIndex].isHiddenInStackView = false
如果你在堆栈视图中有多个视图要管理而不是一堆 if 语句,这将很有帮助。