【问题标题】:UIStackView - layout constraint issues when hiding stack viewsUIStackView - 隐藏堆栈视图时的布局约束问题
【发布时间】:2016-02-12 02:12:17
【问题描述】:

我的应用有 2 个屏幕:

  1. TableViewVC(这里没有堆栈视图)

  2. DetailVC(这里所有嵌套的堆栈视图;请参阅图片链接:Nested StackViews Picture)——注意,这些堆栈视图中有标签和图像。

当您在表格视图中按下一个单元格时,它会将信息从 TableViewVC 传递到 DetailVC。问题在于 在 DetailVC 中隐藏特定的 UIStackViews。 我希望在 DetailVC 中的各种堆栈视图中,只要视图加载后立即隐藏 2 个堆栈视图。所以我在DetailVC中写了这段代码来完成这个:

override func viewDidLoad() {
    super.viewDidLoad()

    self.nameLabel.text = "John"

    self.summaryStackView.hidden = true
    self.combinedStackView.hidden = true
}

一切看起来都不错,但 Xcode 仅在 运行时 给出许多警告。当应用程序未运行时,Storyboard 中没有警告。错误图片请看链接:Picture of Errors

基本上这是很多 UISV-hiding、UISV-spacing、UISV-canvas-connection 错误。如果我在viewDidAppear 中隐藏相同的堆栈视图,这些错误就会消失,但随后会出现本应隐藏的内容,然后将其隐藏。用户短暂地看到视图,然后隐藏起来,这是不好的。

很抱歉无法实际发布图片而不是链接,但仍然不能这样做。

关于如何解决此问题的任何建议?这是针对我真正想要发布到应用商店的应用程序 - 这是我的第一个应用程序,所以任何帮助都会很棒!

编辑/更新 1:

我找到了一个解决此代码的小方法,我将它放在名为 DetailVC 的第二个屏幕中:

// Function I use to delay hiding of views
func delay(delay: Double, closure: ()->()) {
    dispatch_after(
        dispatch_time(
            DISPATCH_TIME_NOW,
            Int64(delay * Double(NSEC_PER_SEC))
        ),
        dispatch_get_main_queue(), closure)
}

// Hide the 2 stack views after 0.0001 seconds of screen loading
override func awakeFromNib() {
    delay(0.001) { () -> () in
        self.summaryStackView.hidden = true
        self.combinedStackView.hidden = true
    }
}

// Update view screen elements after 0.1 seconds in viewWillAppear
override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated)
    delay(0.1) { () -> () in
        self.nameLabel.text = "John"
    }
}

这完全摆脱了 Xcode 中有关布局约束的警告。

它仍然不完美,因为有时我会瞥见应该隐藏的视图 - 它们在屏幕上快速闪烁然后消失。不过这发生得太快了。

关于为什么这会消除警告的任何建议?另外,关于如何改进它以使其完美运行的任何建议???谢谢!

【问题讨论】:

  • 问题是约束冲突。在 viewDidLoad 的末尾尝试self.view.layoutIfNeeded() 之类的东西?
  • @Tim 嘿,感谢您的回复。刚刚尝试了您的建议,警告仍然出现。顺便说一句,我用 viewDidLoad 中的其他内容更新了我的问题——我更新了一个标签和其他一些东西,但为了简单起见,我只放了一个标签。你认为我应该在其他任何地方打电话给layoutIfNeeded吗?
  • 这取决于您在情节提要中的布局。您需要在那里使用自动约束,或者可能在更改发生后以编程方式设置约束。这里有很多关于如何做到这一点的答案。有时有用的另一行是self.view.translatesAutoresizingMaskIntoConstraints = false。你可以在 layoutIfNeeded 之前尝试一下。我只是把我尝试过的所有东西都拍出来,这就是为什么我实际上没有答案。祝你好运!
  • @Tim 刚试过self.view.translatesAutoresizingMaskIntoConstraints = false -- 当我从第一个屏幕单击一个单元格以显示第二个屏幕时,第二个屏幕显示并以半黑屏幕开始,然后完全黑除了导航栏。无论如何感谢您的建议!
  • 为什么不把视图隐藏在故事板中而不是隐藏在 viewDidLoad 中呢?顺便说一句,如果您使用视图控制器,通常不需要 awakeFromNib,请改用 viewDidLoad。

标签: ios swift uistackview


【解决方案1】:

这个错误不是关于隐藏,而是关于模棱两可的约束。您的视图中不得有任何模棱两可的约束。 如果您以编程方式添加它们,您应该准确了解您添加的约束以及它们如何协同工作。 如果您不以编程方式添加它们,而是使用故事板或 xib(这是一个很好的起点),请确保没有约束错误或警告。

UPD:那里的视图结构非常复杂。如果没有看到约束,很难说到底哪里出了问题。但是,我建议您逐步构建视图层次结构,逐个添加视图,并确保没有设计时/运行时警告。 如果处理不当,滚动视图可能会增加另一层复杂性。了解如何在滚动视图中使用约束。 无论如何,所有其他计时技巧都不是解决方案。

【讨论】:

  • 我正在使用情节提要,没有警告或错误。只有在我运行应用程序并转到第二个屏幕时才会出现警告。所以这让我对我需要用约束解决什么感到困惑,因为没有警告。我仍在学习这一切,并尽我所能解决它。请参阅我更新的问题,通过一个小工作解决警告但仍然不完美。请检查一下 --- 有什么建议可以让这项工作更好吗?
  • 我刚刚删除了 4 个具有固定高度约束的 UIView,一切都在设计时消失了。然后在运行时警告再次出现。这太疯狂了。我的意思是屏幕可以完美运行并且没有崩溃 --- 你认为我可以启动应用商店并显示这样的警告吗?
  • @Larry 我不知道苹果会不会拒绝它。
【解决方案2】:

把你的隐藏命令放在viewWillLayoutSubviews() {}

【讨论】:

  • 刚刚又试了一次。隐藏在viewWillAppear 中会导致相同的警告,但使用我在上面添加的delay 函数(延迟0.1 秒)隐藏在“viewWillAppear”中会使所有警告消失。奇怪,不明白为什么。
  • viewWillLayoutSubviews怎么样>
  • 我在viewDidLoadviewWillAppear 都试过了,但都不好。我的代码中的顺序与上面相同,我只是在末尾添加了self.viewWillLayoutSubviews()
  • 您可以将override func viewWillLayoutSubviews() {} 作为覆盖代码,这就是我的意思。我会试着躲在里面。
  • 哦,好的,所以我试了一下,警告消失了!!!唯一的问题是我无法取消隐藏堆栈视图——我将继续使用它来查看是否可以将它们组合在一起。我会让你知道我的想法---谢谢!
【解决方案3】:

我遇到了同样的问题,我通过将最初隐藏视图的高度限制设置为 999 的优先级来解决它。

问题是您的堆栈视图在您的隐藏视图上应用了 0 的高度约束,这与您的其他高度约束冲突。这是错误消息:

Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 
(
    "<NSLayoutConstraint:0x7fa3a5004310 V:[App.DummyView:0x7fa3a5003fd0(40)]>",
    "<NSLayoutConstraint:0x7fa3a3e44190 'UISV-hiding' V:[App.DummyView:0x7fa3a5003fd0(0)]>"
)

给你的身高限制一个较低的优先级可以解决这个问题。

【讨论】:

  • 同意@tsp,很好的答案解决了我的问题,谢谢。
  • 非常感谢能够解释问题以及修复工作原理的答案
  • 奇怪的是,将隐藏代码放在 UIView 动画块中也为我修复了它。不知道为什么。为了安全起见,我都会做这两件事。 :-)
  • 我遇到了同样的问题,我觉得这是正确的答案。谢谢!
  • 唯一的问题是当你的堆栈视图中有非零间距时。间距约束仍然与隐藏约束冲突。
【解决方案4】:

我将所有 UIStackView.hidden 代码从 viewDidLoad 移动到 viewDidAppear 并且破坏约束问题消失了。就我而言,所有冲突的约束都是自动生成的,因此无法调整优先级。

我也用这段代码让它更漂亮:

UIView.animateWithDuration(0.5) {
    self.deliveryGroup.hidden = self.shipVia != "1"
}

编辑:

还需要以下代码来阻止设备旋转时再次发生:

override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)

    self.deliveryGroup.hidden = false

    coordinator.animateAlongsideTransition(nil) {
    context in
        self.deliveryGroup.hidden = self.shipVia != "1"
    }
}

【讨论】:

  • 正如我在上面的问题中所说,viewDidAppear 确实修复了我的错误消息,就像它为你所做的那样。但随后用户会短暂看到应该隐藏的内容。我的视图就像一个由堆栈视图组成的展开/折叠表。如果它隐藏在 viewDidAppear 中,用户将看到堆栈折叠在一起,这是不希望的。这有意义吗?你对解决这个问题有什么想法吗?谢谢。
  • 不,我只是接受这一点并使用 animateWithDuration 所以它看起来是故意的。我相信根本问题只是 UIStackView 的一个错误,可能与嵌套堆栈视图有关。我没有尝试的另一件事是遍历 stackview 的所有子、孙等并设置隐藏标志。
  • 好的,我想我可能会做同样的事情,并将其动画隐藏在 viewDidAppear 中。你看到其他人有类似的问题吗?这就是你认为这是一个错误的原因吗?
【解决方案5】:

我通过将隐藏命令放入 traitCollectionDidChange 来修复它。

override func traitCollectionDidChange(previousTraitCollection: UITraitCollection?) {
    super.traitCollectionDidChange(previousTraitCollection)
    self.item.hidden = true
}

【讨论】:

  • 这行得通!非常感谢您的帮助! :) 我很想知道你是怎么想出来的,这样我就可以成为一个更好的开发人员。我知道一些视图生命周期,但不知道这存在。你能教我这个 traitCollectionDidChange 是如何工作的,以及你是如何发现这是这个问题的正确答案吗?
  • 这可能会导致意外错误。 traitCollectionDidChange 将在用户旋转手机时被调用,在这种情况下会隐藏视图。设置约束优先级,如下面@Raphael 的回答,是一个更正确的解决方案。
【解决方案6】:

您可以使用 UIStackView 的 removeArrangedSubview 和 removeFromSuperview 属性。

在 Objective-C 中:

 [self.topStackView removeArrangedSubview:self.summaryStackView];
 [self.summaryStackView removeFromSuperview];

 [self.topStackView removeArrangedSubview:self.combinedStackView];
 [self.combinedStackView removeFromSuperview];

来自 Apple UIStackView 文档:(https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIStackView_Class_Reference/#//apple_ref/occ/instm/UIStackView/removeArrangedSubview:)

每当视图被添加、删除或插入到排列的Subviews 数组中时,堆栈视图都会自动更新其布局。

  • removeArrangedSubview:此方法从堆栈的arrangedSubviews 数组中删除提供的视图。视图的位置和大小将不再由堆栈视图管理。但是,此方法不会从堆栈的子视图数组中删除提供的视图;因此,视图仍然显示为视图层次结构的一部分。

为防止视图在调用堆栈的 removeArrangedSubview: 方法后出现在屏幕上,请通过调用视图的 removeFromSuperview 方法从子视图数组中显式删除视图,或将视图的隐藏属性设置为 YES。

【讨论】:

    【解决方案7】:

    当 UIViewStack 被隐藏时,如果 UIStackView 的间距属性具有任何非零值,则由 UIStackView 自动生成的约束将引发大量 UISV-hiding、UISV-spacing、UISV-canvas-connection 警告。

    这没有多大意义,几乎可以肯定是一个框架错误。我使用的解决方法是在隐藏组件时将间距设置为零。

    if hideStackView {
        myStackView.hidden = true
        myStackView.spacing = CGFloat(0)
    } else {
        myStackView.hidden = false
        myStackView.spacing = CGFloat(8)
    }
    

    【讨论】:

    • 这个解决方案非常适合我的堆栈视图,它具有动态高度(无法像其他答案那样设置高度约束)
    【解决方案8】:

    这是隐藏嵌套堆栈视图的已知问题。

    这个问题基本上有3个解决方案:

    1. 将间距更改为 0,但您需要记住之前的间距值。
    2. 调用innerStackView.removeFromSuperview(),但您需要记住在哪里插入堆栈视图。
    3. 用至少一个 999 约束将堆栈视图包装在 UIView 中。例如。顶部,领先,尾随@1000,底部@999​​。

    在我看来,第三个选项是最好的。有关此问题、发生原因、不同解决方案以及如何实施解决方案 3 的更多信息,请参阅my answer to a similar question

    【讨论】:

    • 第三个选项效果很好,提供了一个非常干净的解决方案。谢谢分享!
    • 第三个选项确实是最好的解决方案。谢谢!
    • 你成就了我的一天!谢谢你。优先考虑 999 解决了我的问题。
    • @Senseful 谢谢。选项 3 帮助我解决了内存不足的问题。这是帖子。 stackoverflow.com/q/66736006/430690。我在 iOS 14.4 上遇到过这个问题,但只在 iPhone 8 上遇到过。它在 iPhone 11 和 8 Plus 设备上没有出现问题。
    【解决方案9】:

    你试过吗?更改后致电super

    override func viewWillAppear() {
        self.nameLabel.text = "John"
        self.summaryStackView.hidden = true
        self.combinedStackView.hidden = true
        super.viewWillAppear()
    }
    

    【讨论】:

    • 这看起来像是解决方法,而不是修复。第二个问题是真正的修复
    • “第二个问题是真正的解决方案” - 可能您的意思是“答案”,而不是“问题”。无论如何,让我们在这里分享最受好评的答案的链接,以便新加入者使用它 (stackoverflow.com/a/34198061/5242940) 而不是解决方法:)
    【解决方案10】:

    我发现如果您在✨Interface Builder✨ 中设置 hidden 属性,嵌套的 UIStackViews 会显示此行为。我的解决方案是在✨Interface Builder✨中将所有内容设置为不隐藏,并有选择地在 viewWillAppear 中隐藏内容。

    【讨论】:

    • 完美!如果你支持 iOS12,现在到 2020 年它仍然是实际崩溃。只需将堆栈视图设置为在情节提要中可见。
    【解决方案11】:

    我通过将嵌套 UIStackView 的所有隐藏视图存储在一个数组中并将它们从超级视图和排列的子视图中删除来做到这一点。当我希望它们再次出现时,我循环遍历数组并将它们再次添加回来。这是第一步。

    第二步是从父视图中删除嵌套 UIStackView 的视图后,父 UIStackView 仍然不会调整它的高度。您可以通过删除嵌套的 UIStackView 并在之后直接再次添加来解决此问题:

    UIStackView *myStackView;
    NSUInteger positionOfMyStackView = [parentStackView indexOfObject:myStackView];
    [parentStackView removeArrangedSubview:myStackView];
    [myStackView removeFromSuperview];
    [parentStackView insertArrangedSubview:myStackView atIndex:positionOfMyStackView];
    

    【讨论】:

      【解决方案12】:

      因此,这可能只对 0.000001% 的用户有帮助,但这可能是一个限制性问题。

      我最近在使用 UICollectionViewCell 时遇到了这个问题,我忘记检查剪辑到我作为内容视图处理的视图的边界。当您在 IB 中创建 UITableViewCell 时,它会设置一个内容视图,其中默认剪辑到边界。

      重点是,根据您的情况,您可以使用框架和剪辑来实现您的预​​期效果。

      【讨论】:

        【解决方案13】:

        如果您在动画 HIDING 和 SHOWING 子视图时遇到问题,在动画完成中重复 .isHidden 指令可能会有所帮助。有关详细信息,请参阅我的回答 here

        【讨论】:

          猜你喜欢
          • 2021-06-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-12-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多