Mối quan hệ giữa setNeedsLayout, layoutIfNeeded và layoutSubviews của UIView là gì?


105

Bất cứ ai có thể đưa ra một lời giải thích dứt khoát về mối quan hệ giữa UIView's setNeedsLayout, layoutIfNeededlayoutSubviewscác phương pháp? Và một ví dụ triển khai trong đó cả ba sẽ được sử dụng. Cảm ơn.

Điều khiến tôi bối rối là nếu tôi gửi cho chế độ xem tùy chỉnh của mình một setNeedsLayouttin nhắn, điều tiếp theo mà nó gọi sau phương thức này là layoutSubviewsbỏ qua ngay layoutIfNeeded. Từ các tài liệu, tôi mong đợi luồng sẽ là setNeedsLayout> nguyên nhân layoutIfNeededđược gọi> nguyên nhân layoutSubviewsđược gọi.

Câu trả lời:


104

Tôi vẫn đang cố gắng tự tìm ra điều này, vì vậy hãy mang điều này với một số hoài nghi và tha thứ cho tôi nếu nó có sai sót.

setNeedsLayoutlà một cách dễ dàng: nó chỉ đặt một cờ ở đâu đó trong UIView để đánh dấu nó là cần bố trí. Điều đó sẽ buộc layoutSubviewsphải được gọi trên chế độ xem trước khi lần vẽ lại tiếp theo xảy ra. Lưu ý rằng trong nhiều trường hợp, bạn không cần phải gọi điều này một cách rõ ràng, vì thuộc autoresizesSubviewstính. Nếu điều đó được đặt (theo mặc định) thì bất kỳ thay đổi nào đối với khung của chế độ xem sẽ khiến chế độ xem bố trí các chế độ xem phụ của nó.

layoutSubviewslà phương pháp mà bạn làm tất cả những điều thú vị. Nó tương đương drawRectvới bố cục, nếu bạn muốn. Một ví dụ nhỏ có thể là:

-(void)layoutSubviews {
    // Child's frame is always equal to our bounds inset by 8px
    self.subview1.frame = CGRectInset(self.bounds, 8.0, 8.0);
    // It seems likely that this is incorrect:
    // [self.subview1 layoutSubviews];
    // ... and this is correct:
    [self.subview1 setNeedsLayout];
    // but I don't claim to know definitively.
}

AFAIK layoutIfNeededthường không có nghĩa là bị ghi đè trong lớp con của bạn. Đó là một phương thức mà bạn muốn gọi khi bạn muốn một khung nhìn được trình bày ngay bây giờ . Việc triển khai của Apple có thể trông giống như sau:

-(void)layoutIfNeeded {
    if (self._needsLayout) {
        UIView *sv = self.superview;
        if (sv._needsLayout) {
            [sv layoutIfNeeded];
        } else {
            [self layoutSubviews];
        }
    }
}

Bạn sẽ yêu cầu layoutIfNeededmột chế độ xem buộc nó (và các chế độ xem siêu tốc của nó nếu cần) phải được bố trí ngay lập tức.


2
Cảm ơn bạn - rất vui vì cuối cùng ai đó đã trả lời điều này. Trong thời gian chờ đợi, tôi cũng phải nghiên cứu kỹ hơn về setNeedsDisplay - thứ khiến drawRect được gọi - và contentMode. Chỉ contentMode = UIViewContentModeRedraw mới khiến setNeedsDisplay được gọi khi giới hạn của chế độ xem thay đổi. Các lựa chọn ContentMode khác chỉ khiến chế độ xem được thu nhỏ, dịch chuyển một số lượng hoặc căn chỉnh theo một cạnh. UITableViewCell tùy chỉnh của tôi không muốn vẽ lại khi thay đổi hướng, nó sẽ chỉ thay đổi tỷ lệ, cho đến khi tôi đặt contentMode thành UIViewContentModeRedraw.
Tarfa

Tôi nghĩ có lẽ [self.subview1 layoutSubviews] trong mã của bạn nên được thay thế bằng [self.subview1 setNeedsLayout]. Vì layoutSubviews có nghĩa là được ghi đè nhưng không có nghĩa là được gọi từ hoặc đến một chế độ xem khác. Khuôn khổ xác định thời điểm gọi nó (bằng cách nhóm nhiều yêu cầu bố cục lại thành một lệnh gọi để đạt hiệu quả) hoặc bạn thực hiện gián tiếp bằng cách gọi layoutIfNeeded ở đâu đó trong hệ thống phân cấp chế độ xem.
Tarfa

Correction để bình luận trên của tôi: "... Kể từ layoutSubviews có nghĩa là để được ghi đè hoặc gọi từ tự nhưng không có nghĩa là để được gọi từ hoặc xem khác ..."
Tarfa

Tôi thực sự không chắc chắn về [subview1 layoutSubviews]. Nó rất có thể là, giống như drawRect, bạn không nên gọi nó trực tiếp. Nhưng tôi không chắc liệu việc gọi setNeedsLayouttrong giai đoạn bố cục có khiến chế độ xem được bố trí trong cùng một giai đoạn bố cục hay không hay nếu nó bị trì hoãn cho đến giai đoạn tiếp theo. Nó sẽ được tốt đẹp để xem câu trả lời từ một ai đó thực sự hiểu thế nào điều này tất cả các công trình ...
n8gray

34

Tôi muốn thêm vào câu trả lời của n8gray rằng trong một số trường hợp, bạn sẽ cần phải gọi setNeedsLayouttheo sau layoutIfNeeded.

Ví dụ: giả sử bạn đã viết một chế độ xem tùy chỉnh mở rộng UIView, trong đó việc định vị các chế độ xem phụ rất phức tạp và không thể thực hiện được với autoresizingMask hoặc iOS6 AutoLayout. Định vị tùy chỉnh có thể được thực hiện bằng cách ghi đè layoutSubviews.

Ví dụ: giả sử bạn có một chế độ xem tùy chỉnh có một thuộc contentViewtính và một thuộc edgeInsetstính cho phép đặt lề xung quanh contentView. layoutSubviewssẽ trông như thế này:

- (void) layoutSubviews {
    self.contentView.frame = CGRectMake(
        self.bounds.origin.x + self.edgeInsets.left,
        self.bounds.origin.y + self.edgeInsets.top,
        self.bounds.size.width - self.edgeInsets.left - self.edgeInsets.right,
        self.bounds.size.height - self.edgeInsets.top - self.edgeInsets.bottom); 
}

Nếu bạn muốn có thể tạo hiệu ứng thay đổi khung hình bất cứ khi nào bạn thay đổi thuộc edgeInsetstính, bạn cần ghi đè bộ cài edgeInsetsđặt như sau và gọi setNeedsLayouttheo sau layoutIfNeeded:

- (void) setEdgeInsets:(UIEdgeInsets)edgeInsets {
    _edgeInsets = edgeInsets;
    [self setNeedsLayout]; //Indicates that the view needs to be laid out 
                           //at next update or at next call of layoutIfNeeded, 
                           //whichever comes first 
    [self layoutIfNeeded]; //Calls layoutSubviews if flag is set
}

Theo cách đó, nếu bạn làm như sau, nếu bạn thay đổi thuộc tính edgeInsets bên trong một khối hoạt ảnh, thì sự thay đổi khung của contentView sẽ được làm động.

[UIView animateWithDuration:2 animations:^{
    customView.edgeInsets = UIEdgeInsetsMake(45, 17, 18, 34);
}];

Nếu bạn không thêm lệnh gọi layoutIfNeeded trong phương thức setEdgeInsets, hoạt ảnh sẽ không hoạt động vì layoutSubviews sẽ được gọi vào chu kỳ cập nhật tiếp theo, tương đương với việc gọi nó bên ngoài khối hoạt ảnh.

Nếu bạn chỉ gọi layoutIfNeeded trong phương thức setEdgeInsets, sẽ không có gì xảy ra khi cờ setNeedsLayout không được đặt.


4
Hoặc bạn chỉ có thể gọi [self layoutIfNeeded]bên trong khối hoạt ảnh sau khi thiết lập các cạnh.
Scott Fister
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.