Начиная с Xcode 8 и iOS10, представления не имеют правильного размера в viewDidLayoutSubviews



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



но viewDidLayoutSubviews - это!



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



- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];

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


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



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



это баг?

589   12  

12 ответов:

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

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

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

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

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

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

эта и проблема для всех материалов макета, связанных со слоем представления, например cornerRadius, так как ни один autolayout, ни функциями автоматического изменения маски применяется к свойствам слоя.

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

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

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

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

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

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

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

устранение: обернуть все внутри viewDidLayoutSubviews in DispatchQueue.main.async.

// swift 3

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

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

это исправили (ужасно раздражает) вопрос для меня:

- (void) viewDidLayoutSubviews {

    [super viewDidLayoutSubviews];

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

}

Edit / Note: это для полноэкранного ViewController.

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

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

Я уже сообщал об этой проблеме в 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
    }
}

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

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

до

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

Итак, от сделал до воли

странно

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

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

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

согласно новому обновлению в ios это на самом деле ошибка, но мы можем уменьшить это с помощью -

Если вы используете xib с Autolayout в вашем проекте, то вы должны просто обновление кадров в настройках autolayout, пожалуйста, найдите изображение для этого .enter image description here

Comments

    Ничего не найдено.