ChatGPT解决这个技术问题 Extra ChatGPT

Is there a UIView resize event?

I have a view that has rows and columns of imageviews in it.

If this view is resized, I need to rearrange the imageviews positions.

This view is a subview of another view that gets resized.

Is there a way to detect when this view is being resized?

When is the view resized? When the device rotates, or when the user rotates it using multi-touch?
This view is resized when the user taps on a button on another view (master view).

C
Community

As Uli commented below, the proper way to do it is override layoutSubviews and layout the imageViews there.

If, for some reason, you can't subclass and override layoutSubviews, observing bounds should work, even when being kind of dirty. Even worse, there is a risk with observing - Apple does not guarantee KVO works on UIKit classes. Read the discussion with Apple engineer here: When does an associated object get released?

original answer:

You can use key-value observing:

[yourView addObserver:self forKeyPath:@"bounds" options:0 context:nil];

and implement:

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

Really, laying out subviews is exactly what layoutSubviews is for, so observing size changes, while functional, is IMO bad design.
@uliwitness well they do keep changing this stuff. Anyway, now you should use viewWillTransition etc. etc.
viewWillTransition of for UIViewControllers, not UIView.
Is using layoutSubviews still a recommended method if, depending on the current size of the view, different subviews needed to be added/removed and different constraints need to be added/removed?
Well, I think I just answered this for my case. When I do the setup (dependent on size) I need in an override of bounds, it works. When I do it in layoutSubviews it doesn't.
R
Rudolf Adamkovič

In a UIView subclass, property observers can be used:

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

Without subclassing, key-value observation with smart key-paths will do:

var boundsObservation: NSKeyValueObservation?

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

@Ali Why do you think so?
on load frame changes but it looks like bounds does not (I know it is strange and maybe I am dong something wrong )
@Ali: as it has been mentioned a couple of times here: frame is a derived and runtime calculated property. do not override this, unless you have a very smart and knowing reason to do so. otherwise: use bounds or (even better) layoutSubviews.
Such a great way to create a circle. Thanks! override var bounds: CGRect { didSet { layer.cornerRadius = bounds.size.width / 2 }}
Detecting bounds or frame change is not guaranteed to work, depending on where you put your view in the view hierarchy. I'd override layoutSubviews instead. See this and this answers.
g
gwang

Create subclass of UIView, and override layoutSubviews


the trouble with this is that subviews can not only change their size, but they can animate that size change. When UIView runs the animation, it does not call layoutSubviews each time.
@DavidJeske UIViewAnimationOptions has an option called UIViewAnimationOptionLayoutSubviews. I think this maybe help. : )
W
Warren Stringer

Swift 4 keypath KVO -- This is how I detect autorotate and moving to iPad side panel. Should work work any view. Had to observe the UIView's layer.

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()
    //...
}

This solution worked for me. I'm new to Swift and I'm unsure on the \.bounds syntax you used here. What does that mean exactly?
.layer did the trick! Do you know why using view.observe doesn't work?
@d4Rk - I don't know. My guess is that layer bounds less ambiguous than UIView frames. For example, UITableView's contentOffset will affect the final coordinates of its subViews. Not sure.
THIS IS A GREAT ANSWER FOR ME. My case is that I'm using AutoLayout for laying out my views. BUT UICollectionViewFlowLayout forces you to give an explicit itemSize. but when your collectionView size changes (like in my case when I'm showing a toolbar with AutoLayout) - the itemSize remains the same and throws =[ the observer is the cleanest approach I got so far. and the best one!!
r
rekle

You can create a subclass of UIView and override the

setFrame:(CGRect)frame

method. This is the method called when the frame (i.e. the size) of the view is changed. Do something like this:

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

  // Do your custom code here.
}

Sensible and functional. Good call.
FYI This does not work for UIImageView subclass. More info here: stackoverflow.com/questions/19216684/…
Also, setFrame: wasn't called on my UITextView subclass during resizing caused by autorotation whereas layoutSubviews: was. Note: I'm using auto layout & iOS 7.0.
never override setFrame:. frame is a derived property. See my answer
A
Adlai Holler

Pretty old but still a good question. In Apple's sample code, and in some of their private UIView subclasses, they override setBounds roughly like:

-(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];
}

Overriding setFrame: is NOT a good idea. frame is derived from center, bounds, and transform, so iOS will not necessarily call setFrame:.


This is true (in theory). However, setBounds: is not called either when setting the frame property (at least on iOS 7.1). This could be an optimization Apple added to avoid the additional message.
… and bad design on Apple's side not to call their own accessors.
In fact, both frame and bounds are derived from the view's underlying CALayer; they just call through to the layer's getter. And setFrame: sets the layer's frame, while setBounds: sets the layer bounds. So you cannot just override one or the other. Also, layoutSubviews gets called excessively (not just on geometry changes), so it may not always be a good option either. Still looking...
D
Dan Rosenstark

If you're in a UIViewController instance, overriding viewDidLayoutSubviews does the trick.

override func viewDidLayoutSubviews() {
    // update subviews
}

UIView size changed needed, not UIViewController.
@GastónAntonioMontes thanks for that! You might have noticed a relationship between UIView instances and UIViewController instances. So if you have a UIView instance with no VC attached, the other answers are great, but if you happen to be attached to a VC, this is your man. Sorry it's not applicable to your case.