Xcode 8およびiOS10以降、viewDidLayoutSubviewsでビューのサイズが適切に設定されていません


94

Xcode 8では、でviewDidLoad、すべてのビューコントローラーサブビューのサイズが同じ1000x1000のようです。奇妙なことですが、大丈夫ですviewDidLoadが、ビューのサイズを正しく設定するのにこれほど優れた場所ではありません。

しかしviewDidLayoutSubviewsです!

私の現在のプロジェクトでは、ボタンのサイズを印刷しようとしています。

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

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

ログには、myButtonのサイズ(1000x1000)が表示されます!次に、たとえばボタンをクリックしてログオンすると、ログは通常のサイズを示します。

自動レイアウトを使用しています。

バグですか?


1
UIImageViewで同じ問題が発生しました-印刷すると、奇妙なフレーム=(0 0; 1000 1000);が表示されます。私はUITableViewCell内にいて、テーブルビューを更新すると、フレームは期待どおりになります(セルがビューポートから出て再び戻ったときも)。誰もがなぜこれが起こっているのか(デフォルトでは奇妙なフレーム)何か考えがありますか?
Eugen Dimboiu

4
(0, 0, 1000, 1000)バインドされた初期化は、XcodeがIBからのビューをインスタンス化する新しい方法だと思います。Xcode8以前は、ビューはxibで設定されたサイズで作成され、直後に画面に従ってサイズ変更されました。ただし、サイズはデバイスの選択(画面の下部)に依存するため、IBドキュメントに構成されたサイズはありません。実際の問題は、ビューの最終的なサイズを確認できる信頼できる場所はありますか?
マーティン

4
ボタンの角を丸くしていますか?以前にlayoutIfNeeded()を呼び出してみてください。
Eugen Dimboiu 2016

面白い。確かに、ビューフレームを使用して丸い境界線を計算していました。質問に答えなくても動作します。覚えておくと良いヒントです。ありがとう!
Martin

私はuitextfieldのrightview内に画像ボタンを設定するのと同じような問題を抱えていると思います。画像ボタンの高さと幅をテキストフィールドの高さに設定して、アスペクト比を維持し、コンテナからフォールアウトするようにしました。
atlantach_james 2017

回答:


98

現在、Interface Builderを使用すると、ストーリーボード内のすべてのView Controllerのサイズを動的に変更して、特定のデバイスのサイズをシミュレートできます。

この機能の前に、ユーザーは各ビューコントローラのサイズを手動で設定する必要があります。そのため、View Controllerは特定のサイズで保存initWithCoderされ、初期フレームの設定に使用されました。

今、initWithCoderストーリーボードで定義されたサイズを使用せず、ビューコントローラービューとそのすべてのサブビューに1000x1000 pxのサイズを定義しているようです。

ビューは常にこれらのレイアウトソリューションのいずれかを使用する必要があるため、これは問題ではありません。

  • 自動レイアウト、およびすべての制約がビューを正しくレイアウトします

  • autoresizingMask:制約が付加されていない各ビューをレイアウトします(autolayoutとマージンの制約は同じビューで互換性があることに注意してください\ o /!

ただし、自動レイアウト自動サイズ変更マスクのどちらもレイヤープロパティに適用されないため、これ、ビューレイヤーに関連するすべてのレイアウトスタッフにとって問題になります。cornerRadius

この問題に答えるための一般的な方法はviewDidLayoutSubviews、コントローラー内にいるlayoutSubview場合、またはビュー内にある場合に使用することです。この時点で(それらのsuper相対メソッドを呼び出すことを忘れないでください)、すべてのレイアウト関連の処理が完了したことはほぼ確実です!

かなり確かですか?うーん...完全にではありませんが、私が述べたので、私はこの質問をしました。いくつかのケースでは、このメソッドではビューのサイズが1000x1000のままです。私自身の質問に対する答えはないと思います。それに関する最大の情報を与えるために:

1-セルをレイアウトするときにのみ発生します!ではUITableViewCellUICollectionViewCellサブクラス、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
thxですが、ID 28562874のレーダーが見つかりませんでした。レーダーURLを取得できますか?
Joey、

ビューのレイヤーにも、私にとっては魅力のように働きました!
hlynbech

@Joey、私は自分のバグのリンクを取得できるかどうかわかりません。直接のURLが見つからなかったため、他のユーザーが私のレポートを表示できないようです。このSOの回答stackoverflow.com/a/145223/127493によると、バグの優先度を上げる最善の方法は複製を作成することだと思われます。
マーティン

これはAppleの側では愚かではありません。私は完全に自動レイアウトビューコントローラーによって指定されており、いくつかのテキストフィールドとUIViewにはすべてこの愚かな0,0,1000,1000フレームがあります。すべてではありません。これはどのようにQAを回避できますか?彼らが読まない別のレーダーを提出することができると思います。
ahwulf 2016年

3
皆さんの洞察と提案に感謝します。UIStackView中に内部がUICollectionViewCell正しい高さを返さなかったので、私は一日中髪を抜くのに費やしましたviewDidLayoutSubviews。電話をかけてlayoutIfNeededすぐに問題が修正されました。
Ruiz

40

ボタンに角の丸いものを使用していますか?layoutIfNeeded()前に電話してみてください。


1
ああ、もっと担当者を取得しようとしていますか?コメントで述べたように、これは質問の答えにはなりません。しかし、それは私を助けて+1を獲得しました:)
Martin

4
それは将来の誰かを助けるかもしれません、そしてコメントと比べて見つけるのは簡単です
Eugen Dimboiu

1
今私を助けてくれました!
daidai 2016

@daidaiそれを聞いてうれしい!
Eugen Dimboiu

これは機能します!しかし、なぜ?また、適切なフレームサイズを取得するための正しい解決策は何ですか?
Crashalot 2016

24

解決策:すべてをviewDidLayoutSubviewsで囲みますDispatchQueue.main.async

// swift 3

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

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

1
これを入れても機能し-viewDidLoadます...奇妙な回避策
medvedNick 2017

1
おそらく、viewDidLayoutSubviewsの実行時に、システムが実行する必要があることを実行する時間がなかったためです。dispatchを呼び出すと、1つのランループにジャンプし、システムが彼の言葉を終えることができます。あなたが数ミリ秒の睡眠で同じ振る舞いをすることができたときに私が正しいなら
ティボーノア

すべてのUIタスクはメインスレッド内で実行する必要があるため、ソリューションに感謝します!
danywarner 2017

18

私はこれがあなたの正確な質問ではなかったことを知っていますが、viewDidLayoutSubviewsに正しいフレームサイズがあるにもかかわらず、更新時に私のビューの一部がめちゃくちゃになるという同様の問題に遭遇しました。iOS 10リリースノートによると:

「viewにlayoutIfNeededを送信すると、ビューが移動することは想定されていませんが、以前のリリースでは、ビューのtranslatesAutoresizingMaskIntoConstraintsがNOに設定されていて、制約によって配置されている場合、layoutIfNeededはレイアウトを送信する前にビューをレイアウトエンジンに一致するように移動しますこれらの変更はこの動作を修正し、レシーバーの位置と通常そのサイズはlayoutIfNeededの影響を受けません。

一部の既存のコードは、現在修正されているこの誤った動作に依存している可能性があります。iOS 10より前にリンクされたバイナリの動作の変更はありませんが、iOS 10でビルドする場合は、前のレシーバーであったtranslatesAutoresizingMaskIntoConstraintsビューのスーパービューに-layoutIfNeededを送信するか、前にそれを配置してサイズ変更することにより、状況を修正する必要がある場合があります(または、必要な動作に応じて)layoutIfNeeded。

Autoレイアウトを使用して、superを呼び出す前に自分のlayoutSubviewsとダーティレイアウトをオーバーライドするカスタムUIViewサブクラスを持つサードパーティアプリは、iOS 10で再構築するときにレイアウトフィードバックループをトリガーする危険があります。自己のレイアウトのダーティ化をある時点で停止します(この呼び出しは、iOS 10より前のリリースではスキップされたことに注意してください)。

基本的に、translatesAutoresizingMaskIntoConstraintsを使用している場合、ビューの子オブジェクトで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

@Crashalot私の回答で言うように、autolayoutまたはautoresizingMaskを使用すると、UIViewsが正しくレイアウトされます。しかし、特定のビューフレームで特別な計算を実行する必要がある場合、オイゲンの答えは機能layoutIfNeededします。それを呼び出すことです。これは最善の解決策ではないと感じていますが、まだこれ以上の解決策はありません。
Martin

2

実際にviewDidLayoutSubviewsは、ビューのフレームを設定するのに最適な場所でもありません。私が理解している限り、これから行うべき唯一の場所layoutSubviewsは、実際のビューのコード内のメソッドです。私が正しくなかったといいのですが、正しくない場合は誰かが私を訂正してください!


ご回答有難うございます。AppleのドキュメントviewDidLayoutSubviewsはかなりあいまいです。「ディスカッション」の2番目の文は、最後の文と何らかの形で矛盾しています。developer.apple.com/reference/uikit/uiviewcontroller/...
マーティン

0

XibからUIViewControllerを初期化しているとき、この問題はすでにAppleに報告されています。この問題は長い間存在していますが、かなり良い回避策を見つけました。それに加えて、データソースが最初の瞬間に設定されていないときに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()
        }
    }
}

一回限りの発送:

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

スウィズル拡張:

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の代わりに(レイヤー:CALayerの)layoutSublayersをオーバーライドして、正しいフレームを作成します。


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.