Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Subview's bounds are zero in layoutSubviews()

The bounds of a subview of a subview of a custom UIView seem to be 0 in layoutSubviews(), hence using the bounds in layoutSubviews() is a problem.

To show the problem I put a demo on GitHub: SOBoundsAreZero

Here's a direct link to the custom view's implementation: DemoView.swift

The structure of the custom view "DemoView" is like this:

DemoView
    firstLevelSubview
        secondLevelSubview

This structure is being created programmatically using Auto Layout.

When layoutSubviews() is called, the view and the firstLevelSubview have the expected bounds, but the secondLevelSubview's bounds are 0.

I expected all subviews using Auto Layout to have the correct bounds, at least in the last call to layoutSubviews.

The structure is an abstraction of a real case. To avoid the problem, secondLevelSubview could be added as a first level subview to DemoView. Though, this is something, that is not feasible in the real case.

I feel like I'm missing something simple here, even if it's expected behaviour.

like image 845
Felix Lieb Avatar asked Jul 05 '19 10:07

Felix Lieb


2 Answers

I was able to fix this with a call to secondLevelSubview.layoutIfNeeded() in layoutSubviews().

override func layoutSubviews() {
    super.layoutSubviews()

    secondLevelSubview.layoutIfNeeded()

    firstLevelSubview.layer.cornerRadius = bounds.width / 2
    secondLevelSubview.layer.cornerRadius = secondLevelSubview.bounds.width / 2

    print("bounds.width: \(bounds.width), contentView.bounds.width: \(firstLevelSubview.bounds.width), backgroundView.bounds.width: \(secondLevelSubview.bounds.width)")
}

The description of layoutIfNeeded() is:

Use this method to force the view to update its layout immediately. When using Auto Layout, the layout engine updates the position of views as needed to satisfy changes in constraints. Using the view that receives the message as the root view, this method lays out the view subtree starting at the root. If no layout updates are pending, this method exits without modifying the layout or calling any layout-related callbacks.

So, essentially you have an ordering issue here. The subview is scheduled for layout, and Auto Layout will get to it, but it hasn't done it yet. By calling layoutIfNeeded(), you tell Auto Layout to perform the pending layout immediately so that you can get the updated frame information.

Note: You can also just call self.layoutIfNeeded() and that will layout DemoView and all of its subviews. This would be useful if you had many such subviews and didn't want to have to call layoutIfNeeded() on each of them.

like image 129
vacawama Avatar answered Nov 15 '22 21:11

vacawama


First, Your view is being construed without problem cause you have an initializer based on ‘CGRect’ , so it gets its dimension! Second, Your first subview get initialized after its parents view has got its dimensions. First subview dimensions depends on the parent view dimensions which are ready. So no problem here as you mentioned. But meanwhile your first subview is trying to get its dimensions, your second subview also starts to get its dimensions based on first subview which is not ready yet , so it got nothing. I suggest make different functions for each subview. Make your object from the view class with its frame initializer in Your view controller. In ‘viewDidLoad’ call first subview function and in ’viewDidLoadSubView’ call second subview function. Hope it helps.

like image 22
Amir Sk Avatar answered Nov 15 '22 19:11

Amir Sk