เนื่องจาก Xcode 8 และ iOS10 มุมมองจึงไม่ได้รับการปรับขนาดอย่างเหมาะสมบน viewDidLayoutSubviews


95

ดูเหมือนว่าเมื่อเปิด Xcode 8 viewDidLoadมุมมองย่อยของวิวคอนโทรลเลอร์ทั้งหมดจะมีขนาดเท่ากับ 1000x1000 สิ่งที่แปลก แต่โอเคviewDidLoadไม่เคยมีสถานที่ที่ดีกว่าในการปรับขนาดมุมมองอย่างถูกต้อง

แต่viewDidLayoutSubviewsเป็น!

และในโครงการปัจจุบันของฉันฉันพยายามพิมพ์ขนาดของปุ่ม:

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    NSLog(@"%@", self.myButton);
}

บันทึกแสดงขนาด (1000x1000) สำหรับ myButton! จากนั้นถ้าฉันเข้าสู่ระบบด้วยการคลิกปุ่มตัวอย่างเช่นบันทึกจะแสดงขนาดปกติ

ฉันใช้การจัดวางอัตโนมัติ

เป็นบั๊กหรือเปล่า?


1
มีปัญหาเดียวกันกับ UIImageView - เมื่อฉันพิมพ์ฉันได้รับเฟรมแปลก ๆ = (0 0; 1000 1000); ฉันอยู่ใน UITableViewCell และเมื่อฉันรีเฟรชมุมมองตารางเฟรมก็คือสิ่งที่ฉันคาดหวัง (เช่นเมื่อเซลล์ออกไปจากวิวพอร์ตและกลับมาอีกครั้ง) ใครมีความคิดว่าทำไมถึงเกิดขึ้น (กรอบแปลก ๆ โดยค่าเริ่มต้น)?
Eugen Dimboiu

4
ฉันคิดว่าการ(0, 0, 1000, 1000)เริ่มต้นที่ถูกผูกไว้เป็นวิธีใหม่ที่ Xcode สร้างมุมมองจาก IB ก่อน Xcode8 มุมมองจะถูกสร้างขึ้นด้วยขนาดที่กำหนดไว้ใน xib จากนั้นปรับขนาดตามหน้าจอหลังจากนั้น แต่ตอนนี้ไม่มีขนาดที่กำหนดไว้ในเอกสาร IB เนื่องจากขนาดขึ้นอยู่กับการเลือกอุปกรณ์ของคุณ (ที่ด้านล่างของหน้าจอ) คำถามที่แท้จริงคือมีสถานที่ที่เชื่อถือได้ที่สามารถตรวจสอบขนาดสุดท้ายของมุมมองได้หรือไม่?
Martin

4
คุณใช้มุมมนสำหรับปุ่มของคุณหรือไม่? ลองโทรหา layoutIfNeeded () ดูก่อน
Eugen Dimboiu

น่าสนใจ. ฉันใช้กรอบมุมมองเพื่อคำนวณเส้นขอบกลม แม้ว่าจะไม่ตอบคำถาม แต่ก็ไม่ได้ผล เป็นเคล็ดลับที่ดีที่ควรทราบ ขอบคุณ!
Martin

ฉันคิดว่าฉันมีปัญหาที่คล้ายกันในการตั้งค่าปุ่มรูปภาพในมุมมองด้านขวาของ uitextfield ฉันต้องการตั้งค่าความสูงและความกว้างของปุ่มรูปภาพให้เท่ากับความสูงของช่องข้อความเพื่อให้มันคงอัตราส่วนภาพและหลุดออกจากคอนเทนเนอร์
atlantach_james

คำตอบ:


98

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

ก่อนฟังก์ชันนี้ผู้ใช้ควรตั้งค่าขนาดตัวควบคุมมุมมองแต่ละรายการด้วยตนเอง ดังนั้นตัวควบคุมมุมมองจึงได้รับการบันทึกด้วยขนาดที่แน่นอนซึ่งใช้ในinitWithCoderการตั้งค่าเฟรมเริ่มต้น

ตอนนี้ดูเหมือนว่าinitWithCoderจะไม่ใช้ขนาดที่กำหนดไว้ในสตอรีบอร์ดและกำหนดขนาด 1000x1000 px สำหรับมุมมองตัวควบคุมมุมมองและมุมมองย่อยทั้งหมด

นี่ไม่ใช่ปัญหาเนื่องจากมุมมองควรใช้โซลูชันโครงร่างอย่างใดอย่างหนึ่งต่อไปนี้:

  • การจัดวางอัตโนมัติและข้อ จำกัด ทั้งหมดจะจัดวางมุมมองของคุณอย่างถูกต้อง

  • autoresizingMask ซึ่งจะจัดวางแต่ละมุมมองที่ไม่มีข้อ จำกัด ใด ๆ ที่แนบมา ( หมายเหตุการจัดวางอัตโนมัติและข้อ จำกัด ระยะขอบเข้ากันได้ในมุมมองเดียวกัน \ o /! )

แต่นี้เป็นปัญหาสำหรับสิ่งที่รูปแบบทั้งหมดที่เกี่ยวข้องกับมุมมองชั้นเหมือนcornerRadiusตั้งแต่ค่าautolayoutมิได้autoresizing หน้ากากนำไปใช้กับคุณสมบัติชั้น

ในการตอบปัญหานี้วิธีทั่วไปคือใช้viewDidLayoutSubviewsหากคุณอยู่ในคอนโทรลเลอร์หรือlayoutSubviewหากคุณอยู่ในมุมมอง ณ จุดนี้ (อย่าลืมเรียกsuperวิธีสัมพัทธ์) คุณค่อนข้างมั่นใจว่าเค้าโครงทั้งหมดได้ทำเสร็จแล้ว!

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

1- เกิดขึ้นเฉพาะเมื่อวางเซลล์เท่านั้น! ในUITableViewCell& UICollectionViewCellคลาสย่อยlayoutSubviewจะไม่ถูกเรียกหลังจากมุมมองย่อยจะได้รับการจัดวางอย่างถูกต้อง

2- ตามที่ @EugenDimboiu กล่าวไว้ (โปรดเพิ่มคะแนนคำตอบของเขาหากมีประโยชน์สำหรับคุณ) การเรียก[myView layoutIfNeeded]ใช้มุมมองย่อยที่ไม่ได้จัดวางจะจัดวางได้อย่างถูกต้องทันเวลา

- (void)layoutSubviews {
    [super layoutSubviews];
    NSLog (self.myLabel); // 1000x1000 size 
    [self.myLabel layoutIfNeeded];
    NSLog (self.myLabel); // normal size
}

3- ตามความเห็นของฉันนี่เป็นข้อบกพร่องอย่างแน่นอน ฉันส่งไปที่เรดาร์แล้ว (id 28562874)

PS: ฉันไม่ใช่เจ้าของภาษาอังกฤษดังนั้นอย่าลังเลที่จะแก้ไขโพสต์ของฉันหากไวยากรณ์ของฉันควรได้รับการแก้ไข;)

PS2: หากคุณมีทางออกที่ดีกว่าอย่าลังเลที่จะเขียนคำตอบอื่น ฉันจะย้ายคำตอบที่ยอมรับ


3
ขอบคุณ แต่ฉันไม่พบเรดาร์ที่มี id 28562874 ฉันขอ URL เรดาร์ได้หรือไม่
Joey

ทำงานอย่างมีเสน่ห์สำหรับฉันเช่นกันสำหรับเลเยอร์ของมุมมอง!
hlynbech

@ โจอี้ฉันไม่รู้ว่าฉันจะได้รับลิงค์ของจุดบกพร่องของฉัน ฉันไม่พบ URL โดยตรงและดูเหมือนว่าผู้ใช้รายอื่นจะไม่เห็นรายงานของฉัน ตามคำตอบ SO นี้stackoverflow.com/a/145223/127493ดูเหมือนว่าวิธีที่ดีที่สุดในการเพิ่มลำดับความสำคัญของจุดบกพร่องคือการทำซ้ำ
Martin

นี่เป็นเรื่องโง่เกินในส่วนของ Apple ฉันมีตัวควบคุมมุมมองเค้าโครงอัตโนมัติที่ระบุไว้อย่างครบถ้วนและช่องข้อความหลายช่องและ UIView ทั้งหมดมี 0,0,1000,1000 เฟรมที่โง่เขลานี้ แต่ไม่ใช่ทั้งหมด QA นี้จะหนีไปได้อย่างไร ฉันคิดว่าฉันสามารถยื่นเรดาร์อีกอันที่พวกเขาไม่อ่านได้
ahwulf

3
ฉันขอขอบคุณและทุกคนสำหรับข้อมูลเชิงลึกและข้อเสนอแนะของคุณ ฉันใช้เวลาทั้งวันในการดึงผมออกเพราะUIStackViewด้านในUICollectionViewCellไม่ได้คืนความสูงที่ถูกต้องในระหว่างviewDidLayoutSubviewsนั้น การโทรlayoutIfNeededแก้ไขปัญหาทันที
Ruiz

40

คุณใช้มุมมนสำหรับปุ่มของคุณหรือไม่? ลองโทรlayoutIfNeeded()สอบถามดูก่อน


1
อ่าพยายามหาตัวแทนมากขึ้น? ดังที่ฉันได้กล่าวไว้ในความคิดเห็นของฉันสิ่งนี้ไม่ตอบคำถาม อย่างไรก็ตามมันช่วยฉันได้ดังนั้นคุณจึงได้รับ +1 :)
Martin

4
อาจช่วยใครบางคนได้ในอนาคตและง่ายกว่าที่จะมองเห็นเมื่อเปรียบเทียบกับความคิดเห็น
Eugen Dimboiu

1
ช่วยฉันไว้ตอนนี้!
daidai

@daidai ดีใจที่ได้ยิน!
Eugen Dimboiu

ได้ผล! แต่ทำไม? แต่วิธีแก้ปัญหาที่เหมาะสมในการรับขนาดเฟรมที่เหมาะสมคืออะไร
Crashalot

25

การแก้ไข: Wrap ภายในทุกอย่างในviewDidLayoutSubviewsDispatchQueue.main.async

// swift 3

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    DispatchQueue.main.async {
        // do stuff here
    }
}

1
มันใช้งานได้แม้ว่าจะใส่ลงใน-viewDidLoad... วิธีแก้ปัญหาแปลก ๆ
medvedNick

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

เนื่องจากงาน UI ทั้งหมดต้องดำเนินการภายในเธรดหลักขอบคุณสำหรับการแก้ปัญหาของคุณ!
danywarner

18

ฉันรู้ว่านี่ไม่ใช่คำถามที่แน่นอนของคุณ แต่ฉันพบปัญหาที่คล้ายกันซึ่งในการอัปเดตมุมมองของฉันบางส่วนเกิดความสับสนแม้ว่าจะมีขนาดเฟรมที่ถูกต้องใน viewDidLayoutSubviews ตามบันทึกประจำรุ่น iOS 10:

"การส่ง LayoutIfNeeded ไปยังมุมมองไม่คาดว่าจะย้ายมุมมอง แต่ในรีลีสก่อนหน้านี้หากมุมมองนั้นมีการแปล ไปยังทรีย่อยการเปลี่ยนแปลงเหล่านี้จะแก้ไขพฤติกรรมนี้และตำแหน่งของผู้รับและโดยปกติขนาดของมันจะไม่ได้รับผลกระทบจาก layoutIfNeeded

รหัสที่มีอยู่บางรหัสอาจอาศัยพฤติกรรมที่ไม่ถูกต้องซึ่งได้รับการแก้ไขแล้ว ไม่มีการเปลี่ยนแปลงพฤติกรรมสำหรับไบนารีที่เชื่อมโยงก่อน iOS 10 แต่เมื่อสร้างบน iOS 10 คุณอาจต้องแก้ไขสถานการณ์บางอย่างด้วยการส่ง -layoutIfNeeded ไปยังมุมมอง superview ของมุมมอง TranslatesAutoresizingMaskIntoConstraints ที่เป็นตัวรับก่อนหน้าหรืออื่น ๆ ที่วางตำแหน่งและขนาดก่อนหน้า ( หรือหลังจากนั้นขึ้นอยู่กับพฤติกรรมที่คุณต้องการ) layoutIfNeeded.

แอพของบุคคลที่สามที่มีคลาสย่อย UIView ที่กำหนดเองโดยใช้การจัดวางอัตโนมัติที่แทนที่เลย์เอาต์การดูย่อยและเลย์เอาต์ที่สกปรกในตัวเองก่อนที่จะเรียกใช้ซูเปอร์มีความเสี่ยงที่จะทริกเกอร์ลูปการตอบกลับของเลย์เอาต์เมื่อสร้างใหม่บน iOS 10 เมื่อมีการส่งเลย์เอาต์ที่ตามมาอย่างถูกต้อง หยุดการจัดวางที่สกปรกด้วยตัวเองในบางจุด (โปรดทราบว่าสายนี้ถูกข้ามไปในรุ่นก่อน iOS 10) "

โดยพื้นฐานแล้วคุณไม่สามารถเรียกใช้ layoutIfNeeded บนออบเจ็กต์ลูกของ View ได้หากคุณใช้ translatesAutoresizingMaskIntoConstraints - ตอนนี้การเรียก layoutIfNeeded จะต้องอยู่บน superView และคุณยังสามารถเรียกสิ่งนี้ใน viewDidLayoutSubviews ได้


5

หากเฟรมไม่ถูกต้องใน layoutSubViews (ซึ่งไม่ถูกต้อง) คุณสามารถจัดส่งโค้ดแบบ async บนเธรดหลักได้ ซึ่งจะทำให้ระบบมีเวลาพอสมควรในการจัดวาง เมื่อดำเนินการบล็อกที่คุณจัดส่งเฟรมจะมีขนาดที่เหมาะสม


3

สิ่งนี้แก้ไขปัญหา (น่ารำคาญอย่างน่าขัน) สำหรับฉัน:

- (void) viewDidLayoutSubviews {

    [super viewDidLayoutSubviews];

    self.view.frame = CGRectMake(0,0,[[UIScreen mainScreen] bounds].size.width,[[UIScreen mainScreen] bounds].size.height);

}

แก้ไข / หมายเหตุ: สำหรับ ViewController แบบเต็มหน้าจอ


การใช้ขอบเขตหน้าจอหลักไม่ใช่วิธีแก้ปัญหาที่ดีเพราะในหลาย ๆ กรณี viewController ไม่ได้ใช้พื้นที่หน้าจอทั้งหมด นอกจากนี้คุณควรเรียก[super viewDidLayoutSubviews];ใช้วิธีนี้เนื่องจากมีหลายสิ่งที่จัดวางอัตโนมัติโดยมุมมองของตัวเอง
Martin

@ คุณมาร์ตินแนะนำอะไร? เห็นด้วยกับสิ่งนี้ดูเหมือนจะไม่เหมาะ
Crashalot

@Crashalot ตามที่ผมกล่าวว่าในคำตอบของฉันโดยใช้ autolayout หรือ autoresizingMask จะรูปแบบได้อย่างถูกต้องของคุณUIViews แต่ถ้าคุณต้องทำการคำนวณพิเศษในกรอบมุมมองหนึ่งคำตอบของ Eugen ก็ใช้ได้ผล: เรียกlayoutIfNeededมัน ฉันรู้สึกว่านี่ไม่ใช่ทางออกที่ดีที่สุด แต่ฉันก็ยังไม่พบสิ่งที่ดีกว่า
Martin

2

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


ขอบคุณสำหรับคำตอบ. เอกสารประกอบของ Apple viewDidLayoutSubviewsค่อนข้างคลุมเครือ ประโยคที่สองใน "การอภิปราย" ขัดแย้งกับประโยคสุดท้าย developer.apple.com/reference/uikit/uiviewcontroller/…
Martin

0

ฉันได้รายงานปัญหานี้ไปยัง apple แล้วปัญหานี้มีมานานแล้วเมื่อคุณเริ่มต้น UIViewController จาก Xib แต่ฉันพบวิธีแก้ปัญหาที่ค่อนข้างดี นอกจากนั้นฉันพบปัญหานั้นในบางกรณีเมื่อ layoutIfNeeded บน UICollectionView และ UITableView เมื่อไม่ได้ตั้งค่าแหล่งข้อมูลในช่วงเวลาเริ่มต้นและจำเป็นต้องสลับมันด้วย

extension UIViewController {
    open override class func initialize() {
        if self !== UIViewController.self {
            return
        }
        DispatchQueue.once(token: "io.inspace.uiviewcontroller.swizzle") {
            ins_applyFixToViewFrameWhenLoadingFromNib()
        }
    }

    @objc func ins_setView(view: UIView!) {
        // View is loaded from xib file
        if nibBundle != nil && storyboard == nil && !view.frame.equalTo(UIScreen.main.bounds) {
            view.frame = UIScreen.main.bounds
            view.layoutIfNeeded()
        }
        ins_setView(view: view)
    }

    private class func ins_applyFixToViewFrameWhenLoadingFromNib() {
        UIViewController.swizzle(originalSelector: #selector(setter: UIViewController.view),
                                 with: #selector(UIViewController.ins_setView(view:)))
        UICollectionView.swizzle(originalSelector: #selector(UICollectionView.layoutSubviews),
                                 with: #selector(UICollectionView.ins_layoutSubviews))
        UITableView.swizzle(originalSelector: #selector(UITableView.layoutSubviews),
                                 with: #selector(UITableView.ins_layoutSubviews))
     }
}

extension UITableView {
    @objc fileprivate func ins_layoutSubviews() {
        if dataSource == nil {
            super.layoutSubviews()
        } else {
            ins_layoutSubviews()
        }
    }
}

extension UICollectionView {
    @objc fileprivate func ins_layoutSubviews() {
        if dataSource == nil {
            super.layoutSubviews()
        } else {
            ins_layoutSubviews()
        }
    }
}

ส่งครั้งเดียวขยาย:

extension DispatchQueue {

    private static var _onceTracker = [String]()

    /**
     Executes a block of code, associated with a unique token, only once.  The code is thread safe and will
     only execute the code once even in the presence of multithreaded calls.

     - parameter token: A unique reverse DNS style name such as com.vectorform.<name> or a GUID
     - parameter block: Block to execute once
     */
    public class func once(token: String, block: (Void) -> Void) {
        objc_sync_enter(self); defer { objc_sync_exit(self) }

        if _onceTracker.contains(token) {
            return
        }

        _onceTracker.append(token)
        block()
    }
}

ส่วนขยาย Swizzle:

extension NSObject {
    @discardableResult
    class func swizzle(originalSelector: Selector, with selector: Selector) -> Bool {

        var originalMethod: Method?
        var swizzledMethod: Method?

        originalMethod = class_getInstanceMethod(self, originalSelector)
        swizzledMethod = class_getInstanceMethod(self, selector)

        if originalMethod != nil && swizzledMethod != nil {
            method_exchangeImplementations(originalMethod!, swizzledMethod!)
            return true
        }
        return false
    }
}

0

ปัญหาของฉันได้รับการแก้ไขโดยเปลี่ยนการใช้งานจาก

-(void)viewDidLayoutSubviews{
    [super viewDidLayoutSubviews];
    self.viewLoginMailTop.constant = -self.viewLoginMail.bounds.size.height;
}

ถึง

-(void)viewWillLayoutSubviews{
    [super viewWillLayoutSubviews];
    self.viewLoginMailTop.constant = -self.viewLoginMail.bounds.size.height;
}

จาก Did to Will

แปลกสุด ๆ


0

ทางออกที่ดีกว่าสำหรับฉัน

protocol LayoutComplementProtocol {
    func didLayoutSubviews(with targetView_: UIView)
}

private class LayoutCaptureView: UIView {
    var targetView: UIView!
    var layoutComplements: [LayoutComplementProtocol] = []

    override func layoutSubviews() {
        super.layoutSubviews()

        for layoutComplement in self.layoutComplements {
            layoutComplement.didLayoutSubviews(with: self.targetView)
        }
    }
}

extension UIView {
    func add(layoutComplement layoutComplement_: LayoutComplementProtocol) {
        func findLayoutCapture() -> LayoutCaptureView {
            for subView in self.subviews {
                if subView is LayoutCaptureView {
                    return subView as? LayoutCaptureView
                }
            }
            let layoutCapture = LayoutCaptureView(frame: CGRect(x: -100, y: -100, width: 10, height: 10)) // not want to show, want to have size
            layoutCapture.targetView = self
            self.addSubview(layoutCapture)
            return layoutCapture
        }

        let layoutCapture = findLayoutCapture()
        layoutCapture.layoutComplements.append(layoutComplement_)
    }
}

การใช้

class CircleShapeComplement: LayoutComplementProtocol {
    func didLayoutSubviews(with targetView_: UIView) {
        targetView_.layer.cornerRadius = targetView_.frame.size.height / 2
    }
}

myButton.add(layoutComplement: CircleShapeComplement())


0

หากคุณต้องการทำอะไรบางอย่างตามกรอบของมุมมองของคุณ - แทนที่ layoutSubviews และ call layoutIfNeeded

    override func layoutSubviews() {
    super.layoutSubviews()

    yourView.layoutIfNeeded()
    setGradientForYourView()
}

ฉันมีปัญหากับviewDidLayoutSubviews ที่ส่งคืนเฟรมที่ไม่ถูกต้องสำหรับมุมมองของฉันซึ่งฉันต้องการเพิ่มการไล่ระดับสี และ layoutIfNeeded เท่านั้นที่ทำในสิ่งที่ถูกต้อง :)


-1

ตามการอัปเดตใหม่ใน iOS นี่เป็นข้อผิดพลาด แต่เราสามารถลดสิ่งนี้ได้โดยใช้ -

หากคุณใช้ xib กับการจัดวางอัตโนมัติในโปรเจ็กต์ของคุณคุณต้องอัปเดตเฟรมในการตั้งค่าการจัดวางอัตโนมัติโปรดค้นหารูปภาพสำหรับสิ่งนี้ป้อนคำอธิบายภาพที่นี่

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