Поскольку Xcode 8 и iOS10, представления не имеют должного размера на viewDidLayoutSubviews

Кажется, что с Xcode 8, на viewDidLoad, все просмотры viewcontroller имеют одинаковый размер 1000x1000. Странная вещь, но все в порядке, viewDidLoad никогда не было лучшим местом для правильного размера просмотров.

Но viewDidLayoutSubviews есть!

И в моем текущем проекте я пытаюсь напечатать размер кнопки:

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

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

Журнал показывает размер (1000x1000) для myButton! Затем, если я регистрирую нажатие кнопки, например, журнал показывает нормальный размер.

Я использую автозапуск.

Это ошибка?

Ответ 1

Теперь Interface Builder позволяет динамически изменять размер каждого контроллера представления в раскадровке, чтобы имитировать размер определенного устройства.

Перед этой функциональностью пользователь должен вручную установить каждый размер контроллера вида. Таким образом, контроллер просмотра был сохранен с определенным размером, который использовался в initWithCoder для установки начального кадра.

Теперь кажется, что initWithCoder не использует размер, определенный в раскадровке, и определяет размер 1000x1000 px для представления viewcontroller и всех его подзонов.

Это не проблема, потому что представления всегда должны использовать любое из этих решений:

  • autolayout, и все ограничения корректно соответствуют вашим представлениям

  • autoresizingMask, в котором будет отображаться каждый вид, к которому не привязано никакого ограничения (примечание: автоопределение и ограничения по границам теперь совместимы в одном представлении \o/!)

Но это проблема для всех элементов макета, связанных с уровнем представления, например cornerRadius, так как для свойств слоя не применяются ни автомасштабирование, ни маска автоматической проверки.

Чтобы ответить на эту проблему, общий способ - использовать viewDidLayoutSubviews, если вы находитесь в контроллере, или layoutSubview, если вы находитесь в режиме просмотра. На этом этапе (не забудьте вызвать их относительные методы super), вы уверены, что все макеты были сделаны!

Довольно уверен? Хм... не совсем, я заметил, и почему я задал этот вопрос, в некоторых случаях у этого представления все еще есть размер 1000х1000. Я думаю, что нет ответа на мой вопрос. Чтобы предоставить максимальную информацию о нем:

1- это происходит только при выкладке клеток! В подклассах UITableViewCell и UICollectionViewCell layoutSubview не будет вызываться после того, как subviews будут правильно выложены.

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: Если у вас есть лучшее решение, не стесняйтесь писать другой ответ. Я переведу принятый ответ.

Ответ 2

Используете ли вы закругленные углы для своей кнопки? Попробуйте позвонить layoutIfNeeded() раньше.

Ответ 3

Я знаю, что это был не ваш точный вопрос, но я столкнулся с аналогичной проблемой, где, как и при обновлении, некоторые из моих просмотров были испорчены, несмотря на то, что в viewDidLayoutSubviews был правильный размер кадра. В соответствии с примечаниями к выпуску iOS 10:

"Отправка layoutIfNeeded в представление не ожидается, чтобы переместить представление, но в более ранних версиях, если бы translatesAutoresizingMaskIntoConstraints установлен на NO, и если это было расположение по ограничениям, layoutIfNeeded будет перемещать представление в сопоставьте механизм компоновки перед отправкой макета в поддерево. Эти изменения корректируют это поведение, а положение приемников и обычно его размер не будет затронут layoutIfNeeded.

Некоторые существующие коды могут полагаться на это неправильное поведение, которое теперь исправлено. Нет изменений поведения для связанных друг с другом двоичных файлов iOS 10, но при создании на iOS 10 вам, возможно, придется исправить некоторые ситуаций, отправив -layoutIfNeeded для просмотра translatesAutoresizingMaskIntoConstraints, который был предыдущим приемник, или позиционирование и калибровка до (или после, в зависимости от желаемого поведения) layoutIfNeeded.

Приложения сторонних разработчиков с пользовательскими подклассами UIView с использованием автоматической компоновки, которая переопределить layoutSubviews и грязный макет на себе, прежде чем звонить супер подвержены риску запуска схемы обратной связи компоновки при их восстановлении на iOS 10. Когда они правильно отправляются, последующие вызовы layoutSubviews они должны быть уверены, что в какой-то момент перестанут смешивать макет с самим собой (примечание что этот вызов был пропущен в выпуске до iOS 10).

По существу вы не можете вызвать layoutIfNeeded для дочернего объекта View, если вы используете translatesAutoresizingMaskIntoConstraints - теперь вызов layoutIfNeeded должен быть в супервиде, и вы все равно можете вызвать это в viewDidLayoutSubviews.

Ответ 4

Решение: Оберните все внутри viewDidLayoutSubviews в DispatchQueue.main.async.

// swift 3

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

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

Ответ 5

Если кадры не корректны в layoutSubViews (а какие они нет), вы можете отправить асинхронный код кода в основной поток. Это дает системе некоторое время для компоновки. Когда блок, который вы отправляете, выполняется, рамы имеют соответствующие размеры.

Ответ 6

Это фиксировало (смехотворно раздражающую) проблему для меня:

- (void) viewDidLayoutSubviews {

    [super viewDidLayoutSubviews];

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

}

Изменить/Примечание: для полноэкранного ViewController.

Ответ 7

На самом деле viewDidLayoutSubviews также не лучшее место для установки рамки вашего представления. Насколько я понял, отныне единственное место, которое должно быть сделано, - это метод layoutSubviews в действительном коде представления. Мне жаль, что я не прав, кто-то поправьте меня, если это неправда!

Ответ 8

Я уже сообщал об этой проблеме в apple, эта проблема существует уже давно, когда вы инициализируете UIViewController из Xib, но я нашел довольно приятное обходное решение. В дополнение к этому, я обнаружил эту проблему в некоторых случаях, когда layoutIfNeeded на UICollectionView и UITableView, когда источник данных не установлен в начальный момент, а также нужно также swizzle его.

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

Ответ 9

Моя проблема была решена путем изменения использования с

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

to

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

Итак, от Did to Will

Супер странный

Ответ 10

Лучшее решение для меня.

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

Ответ 11

Переопределить layoutSublayers (слоя: CALayer) вместо layoutSubviews в подкатегории ячейки, чтобы иметь правильные кадры

Ответ 12

В соответствии с новым обновлением в ios это на самом деле ошибка, но мы можем уменьшить это, используя -

Если вы используете xib с автозапуском в своем проекте, тогда вам нужно просто обновить фрейм в настройке автозапуска. введите описание изображения здесь