iOS: determine when all children have had layoutSubviews called
Asked Answered
D

2

12

it appears that viewDidLayoutSubviews is called immediately after layoutSubviews is called on a view, before layoutSubviews is called on the subviews of that view. Is there any way of knowing when layoutSubviews has been called on a view and all of its children that also needed their layouts updated?

Dorfman answered 24/12, 2013 at 19:10 Comment(0)
H
4

You shouldn't have to know if the subviews of a subview have updated their layout: That sounds like too tight coupling. Also, each subview might handle the arrangement of their respective subviews differently and might not (need to) call layoutSubviews for its subviews at all. You should only ever have to know about your direct subviews. You should treat them more or less as black boxes and not care whether they have subviews of their own or not.

Hallee answered 24/12, 2013 at 19:20 Comment(5)
I don't care whether or not the view has any subviews, I just want to know when the view's content has been completely updated, and when viewDidLayoutSubviews is called anything inside a subview hasn't been updated yet.Dorfman
@Dorfman AFAIK, a vanilla UIView doesn't give you that information for the reasons I outline above: When a view's content is "fresh" differs from view to view: A UIImageView subclass could potentially asynchronously fetch an image from the network, or a custom "clock view" could refresh itself every millisecond. Subviews are black boxes. The superviews can know that it has finished laying out its direct subviews, that's about it. Whether their contents is up to date is none of the superview's concern. You'd have to make a custom implementation for that, I think.Hallee
I am using a custom implementation. My specific problem is that some of the subviews are performing asynchronous actions and I need to wait for those actions to complete, and then perform other actions. However, in didLayoutSubviews none of the subviews actions have been queued yet (in a dispatch group or something else). I need someway to ensure that the completion actions aren't run until after actions I don't even know about yet have been completed. Or wait until I do know about them.Dorfman
@Dorfman I think to solve your problem, you might have to rethink your architecture. It sounds as if your views are doing a lot of things that your controllers should be doing. Let the controllers do the work of the async operations, and then have them tell your views to refresh. Also, if you need to run some async operations in a certain order (or only when another operation is done), you might want to look at NSOperation(Queue).Hallee
Thanks, I suspected we needed to rethink the architecture. I didn't write the code for the views, but I know there were specific reasons to do it the way we did. It might be easier to work around those issues than this one though.Dorfman
E
0

As @Johannes Fahrenkrug said, you should "treat them as black boxes". But according to my understandings, it is because that Cocoa just can't promise it.

If you really need to be notified when all subviews have done the layout job, here is a hardcore sample may solve your problem. I don't either promise it would work under every situation.

- (void) layoutSubviewsIsDone{
    // Your code here for layoutSubviews is done
}

// Prepare two parameters ahead
int timesOfLayoutSubviews = 0;
BOOL isLayingOutSubviews = NO;

// Override the layoutSubviews function
- (void) layoutSubviews{
     isLayingOutSubviews = YES;  // It's unsafe here!
     // you may move it to appropriate place according to your real scenario

     // Don't forget to inform super
     [super layoutSubviews];
}

// Override the setFrame function to monitor actions of layoutSubviews
- (void) setFrame:(CGRect)frame{
     if(isLayingOutSubviews){
        if(frame.size.width == self.frame.size.width
        && frame.size.height == self.frame.size.height
        && frame.origin.x == self.frame.origin.x
        && frame.origin.y == self.frame.origin.y
        && timesOfLayoutSubviews ==self.subviews.count){
            isLayingOutSubviews = NO;
            timesOfLayoutSubviews = 0;
            [self layoutSubviewsIsDone];  // Detected job done, call your function
    }else{
        timesOfLayoutSubviews++;
    }
}
Epithelium answered 13/8, 2014 at 14:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.