【发布时间】:2011-04-29 09:03:14
【问题描述】:
我有一个视图,其中包含图像视图的行和列。
如果调整了这个视图的大小,我需要重新排列 imageviews 的位置。
这个视图是另一个被调整大小的视图的子视图。
有没有办法检测这个视图何时被调整大小?
【问题讨论】:
-
视图何时调整大小?设备旋转时,还是用户使用多点触控旋转设备时?
-
当用户点击另一个视图(主视图)上的按钮时,此视图会调整大小。
我有一个视图,其中包含图像视图的行和列。
如果调整了这个视图的大小,我需要重新排列 imageviews 的位置。
这个视图是另一个被调整大小的视图的子视图。
有没有办法检测这个视图何时被调整大小?
【问题讨论】:
正如 Uli 在下面评论的那样,正确的做法是覆盖 layoutSubviews 并在那里布局 imageViews。
如果由于某种原因,您不能继承和覆盖layoutSubviews,观察bounds 应该可以工作,即使有点脏。更糟糕的是,观察存在风险——Apple 不保证 KVO 可以在 UIKit 类上工作。在此处阅读与 Apple 工程师的讨论:When does an associated object get released?
原答案:
您可以使用键值观察:
[yourView addObserver:self forKeyPath:@"bounds" options:0 context:nil];
并实施:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (object == yourView && [keyPath isEqualToString:@"bounds"]) {
// do your stuff, or better schedule to run later using performSelector:withObject:afterDuration:
}
}
【讨论】:
viewWillTransition 等等等等。
layoutSubviews?
在UIView 子类中,可以使用属性观察者:
override var bounds: CGRect {
didSet {
// ...
}
}
如果没有子类化,key-value 观察 和 smart key-paths 就可以了:
var boundsObservation: NSKeyValueObservation?
func beginObservingBounds() {
boundsObservation = observe(\.bounds) { capturedSelf, _ in
// ...
}
}
【讨论】:
frame 是派生和运行时计算的属性。不要覆盖这个,除非你有一个非常聪明和知道的理由这样做。否则:使用bounds 或(甚至更好)layoutSubviews。
override var bounds: CGRect { didSet { layer.cornerRadius = bounds.size.width / 2 }}
创建 UIView 的子类,并覆盖 layoutSubviews
【讨论】:
Swift 4 keypath KVO -- 这就是我检测自动旋转并移动到 iPad 侧面板的方式。应该工作任何视图。必须观察 UIView 的层。
private var observer: NSKeyValueObservation?
override func viewDidLoad() {
super.viewDidLoad()
observer = view.layer.observe(\.bounds) { object, _ in
print(object.bounds)
}
// ...
}
override func viewWillDisappear(_ animated: Bool) {
observer?.invalidate()
//...
}
【讨论】:
.layer 成功了!你知道为什么使用view.observe 不起作用吗?
您可以创建 UIView 的子类并覆盖
setFrame:(CGRect)frame
方法。这是当视图的框架(即大小)改变时调用的方法。做这样的事情:
- (void) setFrame:(CGRect)frame
{
// Call the parent class to move the view
[super setFrame:frame];
// Do your custom code here.
}
【讨论】:
setFrame: 在我的UITextView 子类中没有被调用,而layoutSubviews: 是在自动旋转引起的。注意:我使用的是自动布局和 iOS 7.0。
setFrame:。 frame 是派生属性。看我的回答
相当老,但仍然是一个好问题。在 Apple 的示例代码和他们的一些私有 UIView 子类中,它们重写 setBounds 大致如下:
-(void)setBounds:(CGRect)newBounds {
BOOL const isResize = !CGSizeEqualToSize(newBounds.size, self.bounds.size);
if (isResize) [self prepareToResizeTo:newBounds.size]; // probably saves
[super setBounds:newBounds];
if (isResize) [self recoverFromResizing];
}
覆盖setFrame: 不是一个好主意。 frame派生自center、bounds和transform,所以iOS不一定会调用setFrame:。
【讨论】:
setBounds: 在设置 frame 属性时也不会被调用(至少在 iOS 7.1 上)。这可能是 Apple 为避免额外消息而添加的优化。
frame和bounds都是从视图底层的CALayer派生而来的;他们只是调用层的吸气剂。 setFrame: 设置图层的框架,而setBounds: 设置图层边界。因此,您不能只覆盖其中一个。此外,layoutSubviews 被过度调用(不仅仅是几何变化),因此它也可能并不总是一个好的选择。还在寻找...
如果您在 UIViewController 实例中,覆盖 viewDidLayoutSubviews 就可以了。
override func viewDidLayoutSubviews() {
// update subviews
}
【讨论】:
UIView 实例和UIViewController 实例之间的关系。因此,如果您有一个没有附加 VC 的 UIView 实例,那么其他答案很好,但如果您碰巧附加到 VC,这就是您的人选。抱歉,它不适用于您的情况。