Xcode 8 및 iOS10부터 viewDidLayoutSubviews에서보기 크기가 적절하지 않습니다.


94

Xcode 8을 사용 viewDidLoad하면 모든 viewcontroller 하위 뷰의 크기가 1000x1000 인 것 같습니다. 이상한 일이지만 괜찮 viewDidLoad습니다. 뷰의 크기를 올바르게 조정하는 데 더 좋은 곳은 아닙니다.

하지만 viewDidLayoutSubviews그렇습니다!

그리고 현재 프로젝트에서 버튼 크기를 인쇄하려고합니다.

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

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

로그에는 myButton의 크기가 (1000x1000)으로 표시됩니다! 예를 들어 버튼 클릭에 로그온하면 로그에 정상적인 크기가 표시됩니다.

자동 레이아웃을 사용하고 있습니다.

버그입니까?


1
UIImageView와 동일한 문제가 있습니다-인쇄하면 이상한 프레임 = (0 0; 1000 1000) ;. 나는 UITableViewCell 안에 있고, 일단 tableview를 새로 고치면 프레임은 내가 기대하는 것입니다 (또한 셀이 뷰포트를 벗어나 다시 돌아올 때). 누구나 왜 이런 일이 발생하는지 알 수 있습니까 (기본적으로 이상한 프레임)?
Eugen Dimboiu

4
(0, 0, 1000, 1000)바인딩 된 초기화는 Xcode가 IB에서 뷰를 인스턴스화하는 새로운 방식 이라고 생각합니다 . Xcode8 이전에는 xib에서 구성된 크기로 뷰가 생성 된 후 바로 화면에 따라 크기가 조정되었습니다. 그러나 이제 크기는 장치 선택 (화면 하단)에 따라 달라 지므로 IB 문서에는 구성된 크기가 없습니다. 그래서 진짜 질문은 : 뷰의 최종 크기를 확인할 수있는 신뢰할 수있는 장소가 있습니까?
Martin

4
버튼에 둥근 모서리를 사용하고 있습니까? 전에 layoutIfNeeded ()를 호출 해보십시오.
Eugen Dimboiu

흥미 롭군. 나는 실제로 뷰 프레임을 사용하여 둥근 테두리를 계산했습니다. 질문에 답하지 않아도 작동합니다. 명심해야 할 좋은 팁입니다. 감사!
Martin

uitextfield의 rightview 내부에 이미지 버튼을 설정하는 데 비슷한 문제가 있다고 생각합니다. 이미지 버튼의 높이와 너비를 텍스트 필드의 높이로 설정하여 가로 세로 비율을 유지하고 컨테이너에서 떨어지도록하고 싶었습니다.
atlantach_james

답변:


98

이제 Interface Builder를 통해 사용자는 스토리 보드의 모든 뷰 컨트롤러 크기를 동적으로 변경하여 특정 장치의 크기를 시뮬레이션 할 수 있습니다.

이 기능을 사용하기 전에 사용자는 각 뷰 컨트롤러 크기를 수동으로 설정해야합니다. 따라서 뷰 컨트롤러는 initWithCoder초기 프레임을 설정하는 데 사용되는 특정 크기로 저장되었습니다 .

이제 initWithCoder스토리 보드에 정의 된 크기를 사용하지 않고 viewcontroller보기 및 모든 하위보기에 대해 1000x1000 px 크기를 정의하는 것 같습니다 .

뷰는 항상 다음 레이아웃 솔루션 중 하나를 사용해야하므로 이는 문제가되지 않습니다.

  • 자동 레이아웃 및 모든 제약 조건이 뷰를 올바르게 레이아웃합니다.

  • autoresizingMask, 어떤 제약도 연결되지 않은 각 뷰를 레이아웃합니다 ( autolayout 및 margin 제약 조건은 이제 동일한 뷰에서 호환됩니다 \ o /! ).

그러나 이것은 이다 보기 층, 등에 관련된 모든 레이아웃 물건에 대한 문제 cornerRadius도 있기 때문에, 자동 레이아웃자동 크기 마스크 레이어 속성에 적용됩니다.

이 문제에 답하기 위해 일반적인 방법은 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).

추신 : 저는 영어 원어민이 아니므로 문법을 수정해야하는 경우 게시물을 자유롭게 편집 할 수 있습니다.)

PS2 : 더 나은 해결책이 있다면 다른 답을 쓰지 마십시오. 받아 들인 대답을 이동하겠습니다.


3
thx,하지만 ID가 28562874 인 레이더를 찾을 수 없습니다. 레이더 URL을 가질 수 있습니까?
조이

뷰의 레이어도 나에게 매력처럼 작동했습니다!
hlynbech

@Joey, 내 버그의 링크를 얻을 수 있는지 모르겠습니다. 직접 URL을 찾지 못했으며 다른 사용자가 내 보고서를 볼 수없는 것 같습니다. 이 SO 답변 stackoverflow.com/a/145223/127493 에 따르면 버그의 우선 순위를 높이는 가장 좋은 방법은 복제하는 것 같습니다.
Martin

이것은 애플 측에서 어리석은 일이 아닙니다. 자동 레이아웃 뷰 컨트롤러에 의해 완전히 지정되었으며 여러 텍스트 필드와 UIView에 모두이 바보 같은 0,0,1000,1000 프레임이 있습니다. 그러나 전부는 아닙니다. 이것이 QA를 어떻게 벗어날 수 있습니까? 나는 그들이 읽지 않을 또 다른 레이더를 제출할 수 있다고 생각합니다.
ahwulf

3
귀하의 통찰력과 제안에 대해 귀하와 다른 모든 사람들에게 감사드립니다. 의 UIStackView내부가 UICollectionViewCell올바른 높이를 반환하지 않았기 때문에 하루 종일 머리카락을 뽑았습니다 viewDidLayoutSubviews. 전화는 layoutIfNeeded즉시 문제를 해결했습니다.
Ruiz

40

버튼에 둥근 모서리를 사용하고 있습니까? layoutIfNeeded()전에 전화 해보세요 .


1
아하, 더 많은 담당자를 얻으려고? 내 의견에서 말했듯이 이것은 질문에 대한 답이 아닙니다. 당신이 1 : 적립 있도록하지만, 나에게 도움이
마틴

4
그것은 미래에 누군가가 도움이 될 수 있습니다, 그것은 의견에 비해 발견하기 쉽다
오이겐 Dimboiu

1
지금 막 도와 줬어요!
daidai

@daidai 그것을 듣고 기뻐요!
Eugen Dimboiu

이 작동합니다! 근데 왜? 또한 적절한 프레임 크기를 얻기위한 올바른 솔루션은 무엇입니까?
Crashalot

24

해결 방법 : 내부 랩의 모든 것을 viewDidLayoutSubviews에서 DispatchQueue.main.async.

// swift 3

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

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

1
이 경우에도 넣어 노력하고 있습니다 -viewDidLoad... 그런 이상한 해결 방법
medvedNick

1
아마도 viewDidLayoutSubviews 때 시스템이 수행해야 할 작업을 수행 할 시간이 없었기 때문일 것입니다. 디스패치를 ​​호출하면 하나의 런 루프를 점프하여 시스템이 그의 말을 끝낼 수 있습니다. 내가 옳다 경우 몇 밀리 세컨드의 수면과 같은 동작을 얻을 수있을 때
의 Thibaut 노아

모든 UI 작업은 메인 스레드 내에서 수행되어야하므로 솔루션에 감사드립니다!
danywarner

18

이것이 정확한 질문이 아니라는 것을 알고 있지만 업데이트와 마찬가지로 viewDidLayoutSubviews에 올바른 프레임 크기가 있음에도 불구하고 일부 뷰가 엉망이 된 유사한 문제가 발생했습니다. iOS 10 릴리스 노트에 따르면 :

"view에 layoutIfNeeded를 보내는 것은 뷰를 이동시킬 것으로 예상되지 않지만, 이전 릴리스에서 뷰에 translatesAutoresizingMaskIntoConstraints가 NO로 설정되어 있고 제약 조건에 의해 배치 된 경우 layoutIfNeeded는 레이아웃을 보내기 전에 레이아웃 엔진과 일치하도록 뷰를 이동합니다. 이러한 변경 사항은이 동작을 수정하고 수신자의 위치와 일반적으로 해당 크기는 layoutIfNeeded의 영향을받지 않습니다.

일부 기존 코드는 현재 수정 된이 잘못된 동작에 의존 할 수 있습니다. iOS 10 이전에 연결된 바이너리에 대한 동작 변경은 없지만 iOS 10에서 빌드 할 때 -layoutIfNeeded를 이전 수신자였던 translatesAutoresizingMaskIntoConstraints 뷰의 수퍼 뷰에 전송하거나 이전에 위치를 지정하고 크기를 조정하여 일부 상황을 수정해야 할 수 있습니다 ( 또는 원하는 동작에 따라) layoutIfNeeded.

super를 호출하기 전에 자동 레이아웃을 재정의하는 자동 레이아웃을 사용하는 사용자 정의 UIView 하위 클래스가있는 타사 앱 및 super를 호출하기 전에 자체에 대한 더티 레이아웃은 iOS 10에서 다시 빌드 할 때 레이아웃 피드백 루프를 트리거 할 위험이 있습니다. 후속 layoutSubviews 호출을 올바르게 보내면 다음을 확인해야합니다. 어느 시점에서 자신의 레이아웃을 더럽히는 것을 중지합니다 (iOS 10 이전 릴리스에서는이 호출을 건너 뛰었습니다). "

기본적으로 translatesAutoresizingMaskIntoConstraints를 사용하는 경우 View의 자식 개체에서 layoutIfNeeded를 호출 할 수 없습니다. 이제 layoutIfNeeded를 호출하는 것이 superView에 있어야하며 viewDidLayoutSubviews에서 계속 호출 할 수 있습니다.


5

프레임이 layoutSubViews에서 정확하지 않은 경우 (아닙니다) 메인 스레드에서 약간의 코드를 비동기로 전달할 수 있습니다. 이것은 시스템이 레이아웃을 할 시간을줍니다. 전달한 블록이 실행되면 프레임의 크기가 적절합니다.


3

이것은 나를 위해 (어리석게 성가신) 문제를 해결했습니다.

- (void) viewDidLayoutSubviews {

    [super viewDidLayoutSubviews];

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

}

편집 / 참고 : 이것은 전체 화면 ViewController를위한 것입니다.


대부분의 경우 viewController가 전체 화면 공간을 차지하지 않기 때문에 mainScreen 경계를 사용하는 것은 좋은 해결책이 아닙니다. 또한 [super viewDidLayoutSubviews];뷰 자체에 의해 수행되는 많은 자동 레이아웃 작업 때문에이 메서드를 호출해야 합니다
Martin

@Martin 당신은 무엇을 추천합니까? 이것이 이상적이지 않다는 데 동의했습니다.
Crashalot 2016 년

내 대답에서 말했듯이 @Crashalot은 autolayout 또는 autoresizingMask를 사용하면 올바르게 레이아웃됩니다 UIView. 그러나 특정 뷰 프레임에서 특별한 계산을 수행해야하는 경우 Eugen의 대답이 작동 layoutIfNeeded합니다. 나는 한 느낌 이 가장 좋은 해결책이 아니다,하지만, 난 여전히 더 나은를 발견하지 않았습니다.
Martin

2

사실 viewDidLayoutSubviews또한 시야의 틀을 설정하기에 가장 좋은 장소는 아닙니다. 내가 이해하는 한, 지금부터 수행해야 할 유일한 곳 layoutSubviews은 실제 뷰 코드의 메서드입니다. 내가 옳지 않았 으면 좋겠어, 사실이 아니라면 누군가 나를 바로 잡아 줘!


답변 해 주셔서 감사합니다. 에 대한 Apple 문서 viewDidLayoutSubviews는 매우 모호합니다. "토론"의 두 번째 문장은 어떤면에서 마지막 문장과 모순됩니다. developer.apple.com/reference/uikit/uiviewcontroller/...
마틴

0

나는 이미이 문제를 사과에보고했습니다.이 문제는 Xib에서 UIViewController를 초기화 할 때 오랫동안 존재했지만 아주 좋은 해결 방법을 찾았습니다. 그 외에도 데이터 소스가 초기에 설정되지 않았을 때 UICollectionView 및 UITableView에서 layoutIfNeeded가 발생하고 필요한 경우 일부 경우에 그 문제를 발견했습니다.

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()
        }
    }
}

연장 1 회 발송 :

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에서 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 대신 layoutSublayers (레이어 : CALayer)를 재정의하십시오.


0

뷰의 프레임에 따라 무언가를해야하는 경우 -layoutSubviews를 재정의하고 layoutIfNeeded를 호출하십시오.

    override func layoutSubviews() {
    super.layoutSubviews()

    yourView.layoutIfNeeded()
    setGradientForYourView()
}

내가 가진 문제가 있었다 viewDidLayoutSubviews 잘못된 프레임을 반환 내가 그라디언트를 추가 할 필요가있는 내보기 위해입니다. 그리고 layoutIfNeeded만이 옳은 일을했습니다. :)


-1

iOS의 새로운 업데이트에 따라 이것은 실제로 버그이지만 다음을 사용하여 줄일 수 있습니다.

프로젝트에서 자동 레이아웃과 함께 xib를 사용하는 경우 자동 레이아웃 설정에서 프레임업데이트 해야합니다. 이에 대한 이미지를 찾으십시오.여기에 이미지 설명 입력

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.