是否有UIView调整大小事件?


102

我有一个视图,其中包含imageviews的行和列。

如果调整了此视图的大小,则需要重新排列imageviews的位置。

该视图是另一个被调整大小的视图的子视图。

有没有一种方法可以检测何时调整此视图的大小?


何时调整视图大小?设备何时旋转,或者用户使用多点触控旋转设备?
埃文·穆拉斯基

当用户点击另一个视图(主视图)上的按钮时,将调整该视图的大小。
live-love

Answers:


98

正如Uli在下面评论的那样,执行此操作的正确方法是覆盖layoutSubviews并在其中布局imageViews。

如果由于某种原因您不能继承和覆盖layoutSubviews,则观察bounds应该可以进行,即使是脏的。更糟糕的是,存在观察的风险-苹果公司不保证KVO可以在UIKit类上使用。在这里阅读与Apple工程师的讨论:相关对象何时发布?

原始答案:

您可以使用键值观察:

[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:
    }
}

2
确实,布置子视图恰好是layoutSubviews的目的,因此,观察大小变化(虽然起作用)是IMO不良的设计。
uliwitness 2011年

@uliwitness好吧,他们的确在不断改变这些东西。反正现在你应该使用viewWillTransition等等,等等
丹Rosenstark

适用于UIViewControllers(而非UIView)的viewWillTransition。
Armand

layoutSubviews如果根据视图的当前大小,需要添加/删除不同的子视图并且需要添加/删除不同的约束,是否仍使用推荐的方法?
克里斯·普林斯

1
好吧,我想我只是针对我的情况回答了这个问题。当我进行设置时(取决于大小),我需要覆盖范围,它可以工作。当我在layoutSubviews中这样做时,它不是。
克里斯王子,

93

UIView子类中,可以使用属性观察器:

override var bounds: CGRect {
    didSet {
        // ...
    }
}

如果没有子类,则使用智能键路径进行键值观察将可以:

var boundsObservation: NSKeyValueObservation?

func beginObservingBounds() {
    boundsObservation = observe(\.bounds) { capturedSelf, _ in
        // ...
    }
}

2
@Ali为什么这么认为?
RudolfAdamkovič15年

在负载框架上发生变化,但看起来界限没有变化(我知道这很奇怪,也许我错了)
阿里

3
@Ali:正如这里已经提到过的几次:frame是派生和运行时计算的属性。除非您有非常聪明的明了的理由,否则不要覆盖它。否则:使用bounds或(甚至更好)layoutSubviews
auco 2015年

3
建立圈子的好方法。谢谢!override var bounds: CGRect { didSet { layer.cornerRadius = bounds.size.width / 2 }}
SimplGy

4
根据将视图放置在视图层次结构中的位置,不能保证检测boundsframe更改工作。我会改写layoutSubviews。看到这个这个答案。
HuaTham

31

创建UIView的子类,并覆盖layoutSubviews


11
麻烦的是,子视图不仅可以更改其大小,还可以为该大小变化设置动画。UIView运行动画时,它不会每次都调用layoutSubviews。
David Jeske

1
@DavidJeske UIViewAnimationOptions有一个名为UIViewAnimationOptionLayoutSubviews的选项。我认为这可能有所帮助。:)
Neal.Marlin

8

Swift 4关键路径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()
    //...
}

这个解决方案对我有用。我是Swift的新手,不确定在这里使用的\ .bounds语法。这到底是什么意思?
WBuck '18


.layer做到了!您知道为什么使用view.observe不起作用吗?
d4Rk

@ d4Rk-我不知道。我的猜测是,层边界不如UIView框架模糊。例如,UITableView的contentOffset将影响其subViews的最终坐标。不确定。
沃伦·斯金格

这对我来说是一个巨大的答案。我的情况是我正在使用自动版式对视图进行布局。但是,UICollectionViewFlowLayout会强制您提供显式的itemSize。但是,当您的collectionView大小发生变化时(例如,当我使用AutoLayout显示工具栏时)– itemSize保持不变,并抛出= [观察者是我到目前为止最干净的方法。和最好的一个!
Yitzchak

7

您可以创建UIView的子类并覆盖

setFrame:(CGRect)frame

方法。这是在更改视图的框架(即大小)时调用的方法。做这样的事情:

- (void) setFrame:(CGRect)frame
{
  // Call the parent class to move the view
  [super setFrame:frame];

  // Do your custom code here.
}

明智而实用。好决定。
埃尔佐科

1
仅供参考:这不适用于UIImageView子类。这里更多的信息:stackoverflow.com/questions/19216684/...
威廉Entriken

此外,在由于自动旋转而导致的大小调整期间setFrame:没有调用我的UITextView子类,而是layoutSubviews:。注意:我使用的是自动布局和iOS 7.0。
ma11hew28

3
永不超越setFrame:frame是派生的属性。看看我的回答
Adlai Holler 2014年

7

很老,但仍然是一个很好的问题。在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源自centerboundstransform,因此iOS的不一定会调用setFrame:


2
这是正确的(理论上)。但是,setBounds:在设置frame属性时(至少在iOS 7.1上),不会调用。这可能是Apple添加的一项优化措施,以避免出现其他消息。
nschum 2014年

1
……而且苹果方面的不良设计也不能称其为自己的访问器。
osxdirk 2014年

1
事实上,这两个 framebounds从该视图的基本衍生CALayer; 他们只是调用该层的吸气剂。并setFrame:设置图层的框架,同时setBounds:设置图层的边界。因此,您不能仅覆盖其中一个。另外,layoutSubviews被过度调用(不仅涉及几何更改),因此也不一定总是一个好的选择。仍在寻找...
big_m

-3

如果您在UIViewController实例中,则可以通过重写来viewDidLayoutSubviews实现。

override func viewDidLayoutSubviews() {
    // update subviews
}

需要更改UIView大小,而不是UIViewController。
加斯顿·安东尼奥·蒙特斯

@GastónAntonioMontes对此表示感谢!您可能已经注意到UIView实例与UIViewController实例之间的关系。因此,如果您有一个UIView未连接VC 的实例,那么其他答案也不错,但是如果您恰好连接至VC,这就是您的职责。抱歉,这不适用于您的情况。
Dan Rosenstark '17
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.