0

I have a UIView being added to a UIViewController, I want to ensure the views have been fully loaded as I want to access and use the frames of these views. I am working within the UIView and the ViewController is just for testing purposes.

Here is the example I am working with:

I am loading a custom UIView into a UIViewController so am initialising the view in the following way:

In the UIViewController

var slideView: BSlideBar = BSlideBar(frame: CGRect(x: 0, y: 150, width: 320, height: 54))

self.view.addSubview(slideView)

In the UIView

override init(frame: CGRect) {
    super.init(frame: frame)    
    customInit()
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    customInit()
}

public func customInit() {

    Bundle.main.loadNibNamed("BSlideBar", owner: self, options: nil)
    self.addSubview(self.slideView)
    self.slideView.frame = self.bounds
}

I have an XIB file associated with my UIView so am loading the Nib in the custom init function.

This UIView has other UIViews inside and is all being added to the UIViewController. This is all working fine, the issue comes when I try to access the attributes of those views:

Example:

draggableView.layer.cornerRadius = draggableView.frame.width/2

This will make the draggableView a circle and also allows me to base the size of the draggableView on the view it is contained in while maintaining it as a circle. This means my view will stay consistent whatever frame I initialise it with. Annoyingly the frame is always returned as the frame in the XIB file instead of the frame of the loaded UIView. (if I click a button after it has loaded it then returns the expected and desired frame)

Example 2:

self.centreLine.frame = CGRect(x: Int(sidePadding), y: Int(slideView.frame.height/2), width: Int(UIScreen.main.bounds.width - 2 * sidePadding), height: 2)

Once again I want this view to adjust to the size of the view it is contained in. In this case it should stay within the padding of the edges and be exactly in the middle of the view. Once again this isn't the case.

I have tried to use this answer by using:

-(void)awakeFromNib

override func layoutSubviews() {
    super.layoutSubviews()
}

But neither of these seem to be called late enough.

The problem is partly due to being inside a UIView as I would normally called something like this:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
}

I am slightly confused but really interested in finding out why this problem is occurring. The main problem I have is with concisely searching for the problem and have exhausted all my current leads.

sam_smith
  • 6,023
  • 3
  • 43
  • 60
  • https://stackoverflow.com/questions/4501974/uiview-how-to-get-notified-when-the-view-is-loaded – Mohammad Sadiq Aug 01 '17 at 05:23
  • I have already linked this question to my question. It might be I have misunderstood which function is the appropriate one to use. Any additional explanation would be great – sam_smith Aug 01 '17 at 05:26

2 Answers2

1

It's not “fully loaded” that you care about. It's “fully laid out”.

Override layoutSubviews:

override func layoutSubviews() {
    super.layoutSubviews()

    // At this point, all of my direct subviews' frames are set.
    // However, their subviews (my more distant descendants)
    // haven't had their frames updated yet.
}

There is no particularly good way to be notified when the entire view hierarchy has finished layout. You can be sure all descendants have been laid out when you receive drawRect:, but you're not supposed to modify layer properties at that point.

rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • When I tried this the main UIView was laid out by its subviews weren't (as the comment in your answer suggest). You mention that all the descendants will have been laid out on drawRect: is there a time before this when we are allowed to modify layer properties? – sam_smith Aug 01 '17 at 06:22
0

Inside your view class, make a class function that loads the nib and returns the loaded view. Use that for initializing the view

UiView

class func loadViewFromNib() -> BSlideBar 
    guard let nibs = Bundle.main.loadNibNamed("BSlideBar", owner: self, options: nil) as? [UIVIew] else {  assertFailure(): return UIView() }

    return nibs.first as! BSlideBar
}

Initialize your variable in your UIViewVontroller, configure the view then add it to the subviews

var slideBar: BSlideBar = BSlideBar.loadViewFromNib()
slideBar.frame = CGRect(.....)
addSubview(slideBar)

Should work, but I'm going to double check the "why" before I answer that, don't want to give you bad info.

mmr118
  • 496
  • 4
  • 14