如何在Swift iOS Xcode中检测UIView尺寸的变化?

48

我正在尝试使用UIScrollView内的CATiledLayer来进行一些实验。

不知何故,UIScrollView内的UIView的大小被改变成了一个很大的数字。我需要找出究竟是什么原因导致了这种调整。

有没有办法检测到UIView(无论是frame、bounds)或UIScrollView的contentSize大小发生变化?

我尝试过

override var frame: CGRect {
    didSet {
        println("frame changed");
    }
}

在 UIView 的子类中,

虽然 UIView 的大小之后可能被调整,但这个方法只会在应用程序启动时被调用一次。


2
尝试重写 func layoutSubviews()。 - Greg
9个回答

63

1
最适合UIScrollView(因为viewWillLayoutSubviews()不可访问)。只需注意它可能会被多次调用,并小心不要在其中更改边界,否则它将无限循环。 - Yitzchak
10
根据视图在视图层次结构中的位置,无法保证检测到“bounds”更改是否有效。我建议重写layoutSubviews方法来实现。参见答案。 - HuaTham

45

viewWillLayoutSubviews()viewDidLayoutSubviews()会在视图控制器中每当边界发生变化时被调用。


23
这是一个 UIView**Controller** 的方法,而不是所请求的 UIView 的方法。 - Oliver Pearmain

9

您还可以使用KVO:

您可以设置一个KVO,如下所示,其中view是您想要观察框架变化的view

self.addObserver(view, forKeyPath: "center", options: NSKeyValueObservingOptions.New, context: nil)

通过此通知,您可以获取更改内容:

override func observeValueForKeyPath(keyPath: String!, ofObject object: AnyObject!, change: NSDictionary!, context: CMutableVoidPointer) {
    }

observeValueForKeyPath 方法会在所观察的视图的 frame 发生变化时被调用。

同时,记得在你的视图即将被释放时移除观察者:

view.removeObserver(self, forKeyPath:"center")

7
UIKit上的KVO可能随时会出现问题。因此,应该只在最后一种情况下使用,并准备好在iOS未来版本中失败。请查看这位iOS UIKit工程师的答案:https://dev59.com/b2025IYBdhLWcg3wYE-G#6051404 - srik
UIKit 属性通常不符合 KVO 标准,也没有记录说明。我建议避免使用这种方法。 - HuaTham

9
你可以创建一个自定义类,并使用闭包来轻松获取更新后的矩形。特别适用于处理需要提供CGRect的类(如CAGradientLayer):

GView.swift:

import Foundation
import UIKit

class GView: UIView {

    var onFrameUpdated: ((_ bounds: CGRect) -> Void)?

    override func layoutSublayers(of layer: CALayer) {
      super.layoutSublayers(of: layer)
      self.onFrameUpdated?(self.bounds)
    }
}

示例用法:

let headerView = GView()
let gradientLayer = CAGradientLayer()
headerView.layer.insertSublayer(gradientLayer, at: 0)
    
gradientLayer.colors = [
    UIColor.mainColorDark.cgColor,
    UIColor.mainColor.cgColor,
]
    
gradientLayer.locations = [
    0.0,
    1.0,
]
    
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
    
headerView.onFrameUpdated = { _ in // here you have access to `bounds` and `frame` with proper values
    gradientLayer.frame = headerView.bounds
}

如果你不通过代码添加视图,你可以在Storyboard中设置Custom Class属性为 GView
请注意,选择名字GView是公司的一个措施,也许选择像FrameObserverView这样的名称会更好。

3
这是一个简单且不太繁琐的解决方案:你可以在 layoutSubviews 方法中记住视图的最后一次大小,将其与新的大小进行比较,当你确定大小已更改时执行某些操作。
/// Create this as a private property in your UIView subclass
private var lastSize: CGSize = .zero

open override func layoutSubviews() {
    // First call super to let the system update the layout
    super.layoutSubviews()
    
    // Check if:
    // 1. The view is part of the view hierarchy
    // 2. Our lastSize var doesn't still have its initial value
    // 3. The new size is different from the last size
    if self.window != nil, lastSize != .zero, frame.size != lastSize {
        // React to the size change
    }
    
    lastSize = frame.size
}

请注意,您不必包含self.window != nil的检查。但是我假设在大多数情况下,您只对作为视图层次结构一部分的视图的大小更改感兴趣。
同时请注意,如果您希望在初始显示视图时被通知有关第一个大小更改的事件,可以删除lastSize != .zero的检查。通常我们对此事件不感兴趣,而只对由设备旋转或特征集合变化引起的后续大小更改感兴趣。
祝您使用愉快!

2
对于UIView,就像这样简单:
    override func layoutSubviews() {
        super.layoutSubviews()

        setupYourNewLayoutHereMate()
    }

1
这并不容易。layoutSubviews 可能会被多次调用。此外,视图的 framebounds 属性可能为 .zero。因此,每次调用时都必须进行检查,并采取适当的行动(例如,删除/重建层)。 - Womble

2
答案是正确的,尽管对于我的情况,我在故事板中设置的约束条件导致UIView的大小发生变化,而没有调用任何检测函数。

也许我误解了。但据我所知,任何由于约束而发生的事情都应该在布局预览中显示-在助理编辑器中。 - simons

1
你可以使用FrameObserver Pod。
它不使用KVO或方法交换,因此如果UIKit的底层实现发生更改,它不会破坏你的代码。
whateverUIViewSubclass.addFrameObserver { frame, bounds in // get updates when the size of view changes
    print("frame", frame, "bounds", bounds)
}

您可以在UIView实例或其任何子类上调用它,例如UILabelUIButtonUIStackView等。

-2

第一步:viewWillLayoutSubviews

被调用以通知视图控制器,其视图即将布局其子视图

当视图的边界发生变化时,视图会调整其子视图的位置。您的视图控制器可以重写此方法,在视图布局其子视图之前进行更改。此方法的默认实现不执行任何操作。

第二步:viewDidLayoutSubviews

被调用以通知视图控制器,其视图刚刚完成布局其子视图。

当视图控制器的视图的边界发生变化时,视图会调整其子视图的位置,然后系统调用此方法。但是,调用此方法并不表示已调整视图的子视图的个体布局。每个子视图都负责调整其自己的布局。

您的视图控制器可以重写此方法,在视图布局其子视图后进行更改。此方法的默认实现不执行任何操作。

上述这些方法在UIView的边界发生更改时调用


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接