layoutSubviews เรียกว่าเมื่อใด


268

ฉันมีมุมมองแบบกำหนดเองที่ไม่ได้รับlayoutSubviewข้อความระหว่างการเคลื่อนไหว

ฉันมีมุมมองที่เต็มหน้าจอ มีมุมมองย่อยที่กำหนดเองที่ด้านล่างของหน้าจอที่ปรับขนาดได้อย่างถูกต้องในตัวสร้างส่วนต่อประสานหากฉันเปลี่ยนความสูงของแถบนำทาง layoutSubviewsถูกเรียกเมื่อมุมมองถูกสร้างขึ้น แต่จะไม่เกิดขึ้นอีก มุมมองย่อยของฉันวางไว้อย่างถูกต้อง ถ้าฉันปิดแถบสถานะระหว่างการโทรระบบlayoutSubviewsจะไม่เรียกมุมมองย่อยเลยแม้ว่ามุมมองหลักจะทำให้การปรับขนาดเป็นภาพเคลื่อนไหว

ภายใต้สถานการณ์ใดที่layoutSubviewsเรียกว่าจริง?

ฉันได้autoresizesSubviewsตั้งค่าเป็นNOสำหรับมุมมองที่กำหนดเองของฉัน และในตัวสร้างอินเทอร์เฟซฉันมีเสาด้านบนและด้านล่างและชุดลูกศรแนวตั้ง


อีกส่วนหนึ่งของปริศนาคือหน้าต่างต้องเป็นกุญแจ:

[window makeKeyAndVisible];

มิฉะนั้นมุมมองย่อยจะไม่ถูกปรับขนาดโดยอัตโนมัติ

คำตอบ:


497

ฉันมีคำถามคล้าย ๆ กัน แต่ไม่พอใจกับคำตอบ (หรือสิ่งใด ๆ ที่ฉันสามารถหาได้จากอินเทอร์เน็ต) ดังนั้นฉันจึงลองปฏิบัติจริงและนี่คือสิ่งที่ฉันได้รับ:

  • initไม่ทำให้layoutSubviewsถูกเรียก (duh)
  • addSubview:ทำให้ layoutSubviewsถูกเรียกในมุมมองที่ถูกเพิ่มมุมมองที่ถูกเพิ่มลงใน (มุมมองเป้าหมาย) และมุมมองย่อยทั้งหมดของเป้าหมาย
  • setFrame เรียกดูอย่างชาญฉลาดlayoutSubviewsในมุมมองที่มีการตั้งค่าเฟรมก็ต่อเมื่อพารามิเตอร์ขนาดของเฟรมแตกต่างกัน
  • การเลื่อน UIScrollView ทำให้layoutSubviewsถูกเรียกบน scrollView และ superview
  • การหมุนอุปกรณ์เรียกเฉพาะ layoutSubviewในมุมมองหลัก (มุมมองหลักของมุมมองที่ตอบสนองคอนโทรลเลอร์)
  • การปรับขนาดมุมมองจะเรียกlayoutSubviewsใช้ซูเปอร์วิว

ผลลัพธ์ของฉัน - http://blog.logichigh.com/2011/03/16/when-does-layoutsubviews-get-called/


1
คำตอบที่ดี ฉันสงสัยมาlayoutSubviewsตลอด ไม่initWithFrame:ก่อให้เกิดlayoutSubviewsจะเรียกว่า?
Robert

2
@ โรเบิร์ต - ฉันใช้ initWithFrame ...
BadPirate

8
@BadPirate: ใช่ ตามที่การทดสอบของฉันถ้าคุณปรับขนาดview1.1ที่เรียกว่าlayoutSubviewsของview1แล้วของlayoutSubviews view1.1สายนี้ไม่ได้แพร่กระจายไปเรื่อย ๆ เพื่อ superviews เรียกมันในview1.1.1การโทรเพียงlayoutSubviewsบนและview1.1 view1.1.1เพียงแค่ขยับโดยไม่เปลี่ยนขนาดก็ไม่ได้เรียกร้องlayoutSubviewsใด ๆ
João Portela

1
viewDidLoad ไม่ได้เรียกใช้บน UIView (แต่ UIViewController) ดูไม่โหลดถูกเรียกหลังจากเริ่มต้น UIView แล้ว
BadPirate

2
อ้างอิงจากการทดสอบของฉัน, กฎข้อที่สองอาจจะไม่ถูกต้อง: เมื่อผมเพิ่มview1.2เข้าไปview1, layoutSubviewsของview1.2และview1จะเรียกว่า แต่layoutSubviewsของview1.1ไม่ได้เรียกว่า ( view1.1และview1.2เป็นมุมมองย่อยของview1) นั่นคือไม่ subviews ทั้งหมดของมุมมองที่เป้าหมายจะเรียกว่าlayoutSubviewsวิธีการ
HongchaoZhang

100

จากคำตอบก่อนหน้านี้โดย @BadPirate ฉันได้ทดลองเพิ่มเติมอีกเล็กน้อยและได้รับคำชี้แจง / การแก้ไข ฉันพบว่าlayoutSubviews:จะถูกเรียกดูในกรณีที่:

  • ขอบเขตของตัวเอง (ไม่ใช่กรอบ) เปลี่ยนไป
  • ขอบเขตของหนึ่งในมุมมองย่อยโดยตรงเปลี่ยนไป
  • มุมมองย่อยจะถูกเพิ่มในมุมมองหรือลบออกจากมุมมอง

รายละเอียดที่เกี่ยวข้อง:

  • ขอบเขตจะถือว่าการเปลี่ยนแปลงเฉพาะในกรณีที่ค่าใหม่ที่แตกต่างกันรวมถึงต้นกำเนิดที่แตกต่างกัน สังเกตโดยเฉพาะนั่นคือเหตุผลlayoutSubviews:ถูกเรียกเมื่อใดก็ตามที่ UIScrollView เลื่อนเนื่องจากจะทำการเลื่อนโดยการเปลี่ยนจุดเริ่มต้นของขอบเขต
  • การเปลี่ยนเฟรมจะเปลี่ยนขอบเขตก็ต่อเมื่อขนาดเปลี่ยนไปเนื่องจากนี่เป็นเพียงสิ่งเดียวที่แพร่กระจายไปยังคุณสมบัติขอบเขต
  • การเปลี่ยนแปลงขอบเขตของมุมมองที่ยังไม่อยู่ในลำดับชั้นของมุมมองจะส่งผลให้มีการเรียกใช้layoutSubviews: เมื่อมุมมองถูกเพิ่มลงในลำดับชั้นมุมมองในที่สุดที่สุด
  • และเพื่อความสมบูรณ์: ทริกเกอร์เหล่านี้ไม่ได้เรียกใช้ layoutSubviews โดยตรงแต่เป็นการเรียกsetNeedsLayoutซึ่งกำหนด / ยกธง การวนซ้ำของการวนซ้ำแต่ละครั้งสำหรับมุมมองทั้งหมดในลำดับชั้นของมุมมองแฟล็กนี้จะถูกตรวจสอบ สำหรับแต่ละมุมมองที่พบแฟล็ก layoutSubviews:จะถูกเรียกบนและแฟล็กจะถูกรีเซ็ต มุมมองที่สูงขึ้นตามลำดับชั้นจะถูกตรวจสอบ / เรียกก่อน

5
ไม่สามารถโหวตคำตอบนี้ได้เพียงพอ ควรเป็นคำตอบอันดับต้น ๆ กฎสามข้อที่กำหนดคือทั้งหมดที่คุณต้องการ ฉันไม่เคยเจอเลย์เอาท์ดูพฤติกรรมที่กฎเหล่านี้ไม่ได้อธิบายอย่างสมบูรณ์แบบ
Pärserk

1
ฉันไม่คิดว่า layoutSubviews จะถูกเรียกเมื่อขอบเขตของมุมมองย่อยโดยตรงเปลี่ยนไป ฉันคิดว่าการเรียกใช้ layoutSubviews เป็นเพียงผลข้างเคียงจากการทดสอบของคุณ ในบางกรณี layoutSubviews จะไม่ถูกเรียกเมื่อขอบเขตมุมมองย่อยเปลี่ยนไป โปรดตรวจสอบคำตอบของ frogcjn เนื่องจาก anwser ของเขาอ้างอิงจากเอกสารของ Apple แทนที่จะเป็นเพียงการทดลอง
Simon Backx

20

https://developer.apple.com/library/prerelease/tvos/documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/CreatingViews/CreatingViews.html#//apple_ref/doc/uid/TP40009503-CH5-SW1

การเปลี่ยนแปลงเค้าโครงสามารถเกิดขึ้นได้ทุกครั้งที่มีเหตุการณ์ต่อไปนี้เกิดขึ้นในมุมมอง:

ก. ขนาดของสี่เหลี่ยมผืนผ้าขอบเขตของมุมมองเปลี่ยนไป
ข. การเปลี่ยนแปลงการวางแนวอินเทอร์เฟซเกิดขึ้นซึ่งโดยปกติจะทำให้เกิดการเปลี่ยนแปลงในมุมมองขอบเขตของมุมมองรูท
ค. ชุดของเลเยอร์ย่อยของ Core Animation ที่เกี่ยวข้องกับการเปลี่ยนแปลงเลเยอร์ของมุมมองและต้องใช้เค้าโครง
ง. แอปพลิเคชันของคุณบังคับให้เค้าโครงเกิดขึ้นโดยการเรียกใช้  setNeedsLayout หรือ  layoutIfNeeded วิธีการของมุมมอง
จ. แอปพลิเคชันของคุณบังคับให้เค้าโครงโดยการเรียกใช้  setNeedsLayout เมธอดของวัตถุชั้นต้นแบบของมุมมอง


นอกจากนี้สิ่งสำคัญที่ต้องระบุคือไม่มีเหตุการณ์ใดที่ถูกเรียกเมื่อยังไม่ได้เพิ่มมุมมองลงในสแต็กมุมมอง คุณรวมสิ่งนี้ไว้กับคำว่า "can" แต่จะเรียกเฉพาะในรอบถัดไปที่มีอยู่ของเธรดหลักของแอปพลิเคชันเมื่อมีการทำเครื่องหมายว่าจำเป็นต้องจัดวางโดยหนึ่งในเหตุการณ์เหล่านั้น
user1122069

13

บางประเด็นในคำตอบของ BadPirateเป็นความจริงเพียงบางส่วน:

  1. สำหรับaddSubViewจุด

    addSubview ทำให้มีการเรียก layoutSubview ในมุมมองที่ถูกเพิ่มมุมมองที่ถูกเพิ่มไปยัง (มุมมองเป้าหมาย) และมุมมองย่อยทั้งหมดของเป้าหมาย

    ขึ้นอยู่กับการปรับขนาดมาสก์อัตโนมัติของมุมมอง (มุมมองเป้าหมาย) หากมีการเปิดขนาดมาสก์อัตโนมัติจะมีการเรียก layoutSubview ในแต่ละรายการaddSubviewรายการ หากไม่มีการปรับขนาดมาสก์อัตโนมัติ layoutSubview จะถูกเรียกก็ต่อเมื่อขนาดเฟรมของมุมมอง (มุมมองเป้าหมาย) เปลี่ยนไป

    ตัวอย่าง: หากคุณสร้าง UIView โดยใช้โปรแกรม (ไม่มีการปรับขนาดมาสก์อัตโนมัติตามค่าเริ่มต้น) LayoutSubview จะถูกเรียกก็ต่อเมื่อเฟรม UIView ไม่เปลี่ยนแปลงในทุกๆ addSubviewเปลี่ยนแปลงไม่ได้อยู่ในทุก

    ด้วยเทคนิคนี้ทำให้ประสิทธิภาพของแอปพลิเคชันเพิ่มขึ้นด้วย

  2. สำหรับจุดหมุนอุปกรณ์

    การหมุนอุปกรณ์เท่านั้นที่เรียกใช้ layoutSubview บนมุมมองหลัก (มุมมองหลักของ response viewController)

    สิ่งนี้จะเป็นจริงก็ต่อเมื่อ VC ของคุณอยู่ในลำดับชั้น VC (รูทที่window.rootViewController) ซึ่งเป็นกรณีที่พบบ่อยที่สุด ใน iOS 5 หากคุณสร้าง VC แต่ไม่ได้เพิ่มลงใน VC อื่น VC นี้จะไม่สังเกตเห็นเมื่ออุปกรณ์หมุน ดังนั้นมุมมองจะไม่ถูกสังเกตโดยการเรียก layoutSubviews


9

ฉันติดตามการแก้ปัญหาไปจนถึงการยืนยันของ Interface Builder ว่าไม่สามารถเปลี่ยนสปริงได้ในมุมมองที่มีการเปิดองค์ประกอบหน้าจอจำลอง (แถบสถานะ ฯลฯ ) เนื่องจากสปริงปิดอยู่สำหรับมุมมองหลักมุมมองนั้นจึงไม่สามารถเปลี่ยนขนาดได้และด้วยเหตุนี้จึงถูกเลื่อนลงจนสุดเมื่อแถบในการโทรปรากฏขึ้น

การปิดคุณลักษณะจำลองจากนั้นปรับขนาดมุมมองและการตั้งค่าสปริงอย่างถูกต้องทำให้เกิดภาพเคลื่อนไหวและวิธีการของฉันถูกเรียก

ปัญหาเพิ่มเติมในการดีบักนี้คือตัวจำลองจะออกจากแอปเมื่อสถานะการโทรถูกสลับผ่านเมนู ออกจากแอป = ไม่มีดีบักเกอร์


คุณกำลังบอกว่า layoutSubviews ถูกเรียกเมื่อปรับขนาดมุมมองใช่หรือไม่? ฉันคิดเสมอว่ามันไม่ใช่ ...
Andrey Tarantsov

มันคือ. เมื่อมีการปรับขนาดมุมมองจำเป็นต้องดำเนินการบางอย่างกับมุมมองย่อย หากคุณไม่ได้จัดเตรียมไว้ระบบจะเคลื่อนย้ายโดยอัตโนมัติโดยใช้สปริงเสา ฯลฯ
Steve Weller


5

คุณได้ดู layoutIfNeeded แล้วหรือยัง?

ตัวอย่างเอกสารอยู่ด้านล่าง ภาพเคลื่อนไหวทำงานได้หรือไม่หากคุณเรียกวิธีนี้อย่างชัดเจนในระหว่างการเคลื่อนไหว

layoutIfNeeded วางมุมมองย่อยหากจำเป็น

- (void)layoutIfNeeded

การอภิปรายใช้วิธีนี้เพื่อบังคับเค้าโครงของมุมมองย่อยก่อนวาดภาพ

มีจำหน่ายใน iPhone OS 2.0 และใหม่กว่า


2

เมื่อย้ายแอป OpenGL จาก SDK 3 เป็น 4 จะไม่มีการเรียก layoutSubviews อีกต่อไป หลังจากลองผิดลองถูกมากมายในที่สุดฉันก็เปิด MainWindow.xib เลือกวัตถุ Window ในตัวตรวจสอบเลือกแท็บ Window Attributes (ซ้ายสุด) และทำเครื่องหมายที่ "Visible at launch" ดูเหมือนว่าใน SDK 3 จะยังคงใช้เพื่อทำให้เกิดการเรียก layoutSubViews แต่ไม่ใช่ใน 4

6 ชั่วโมงแห่งความขุ่นมัวสิ้นสุดลง


คุณทำคีย์หน้าต่างหรือไม่ ถ้าไม่เช่นนั้นอาจทำให้สิ่งที่น่าสนใจทั้งหมดไม่เกิดขึ้น
Steve Weller

-2

กรณีที่ค่อนข้างคลุมเครือ แต่อาจสำคัญเมื่อlayoutSubviewsไม่เคยถูกเรียกคือ:

import UIKit

class View: UIView {

    override class var layerClass: AnyClass { return Layer.self }

    class Layer: CALayer {
        override func layoutSublayers() {
            // if we don't call super.layoutSublayers()...
            print(type(of: self), #function)
        }
    }

    override func layoutSubviews() {
        // ... this method never gets called by the OS!
        print(type(of: self), #function)
    }
}

let view = View(frame: CGRect(x: 0, y: 0, width: 100, height: 100))

2
ทำไมคำตอบนี้ถึงอยู่ที่นี่?
Dominik Bucher
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.