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


94

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

Але viewDidLayoutSubviewsє!

І на своєму поточному проекті я намагаюся надрукувати розмір кнопки:

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

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

Журнал показує розмір (1000x1000) для myButton! Тоді, якщо я вхожу на кнопку, натисніть, наприклад, журнал показує нормальний розмір.

Я використовую авторозкладку.

Це помилка?


1
З тією ж проблемою, що і з UIImageView - коли я друкую, я отримую дивний кадр = (0 0; 1000 1000) ;. Я всередині UITableViewCell, і як тільки я оновлюю огляд таблиці, я очікую, що це буде (також коли клітина виходить із вікна перегляду та повертається знову). Хтось має уявлення, чому це відбувається (дивний кадр за замовчуванням)?
Євген Дімбой

4
Я думаю, що (0, 0, 1000, 1000)пов'язана ініціалізація - це новий спосіб, коли Xcode інстанціює погляди з ІБ. Перед Xcode8 було створено представлення з налаштованим розміром у xib, а потім змінити розмір відповідно до екрану. Але тепер у документі IB немає налаштованого розміру, оскільки розмір залежить від вибору вашого пристрою (внизу екрана). Отже, справжнє питання: чи є надійне місце, де можна було б перевірити остаточний розмір поглядів?
Мартін

4
ти використовуєш закруглені кути для своєї кнопки? Спробуйте зателефонувати layoutIfNeeded () раніше.
Євген Дімбой

Цікаво. Я дійсно використовував кадр перегляду для обчислення круглої межі. Навіть якщо він не відповідає на питання, це спрацює. Це хороша порада, яку слід пам’ятати. Дякую!
Мартін

Я думаю, що у мене виникають подібні проблеми із налаштуванням кнопки зображення всередині правого огляду uitextfield. Я хотів встановити висоту і ширину кнопки зображення на висоту текстового поля, щоб вона підтримувала співвідношення сторін і випадала з контейнера.
atlantach_james

Відповіді:


98

Тепер Interface Builder дозволяє користувачеві динамічно змінювати розмір кожного контролера перегляду в дошці розкадрування, щоб імітувати розмір певного пристрою.

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

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

Це не є проблемою, оскільки представлення завжди повинні використовувати будь-яке з цих варіантів компонування:

  • автоматичне розміщення, і всі обмеження будуть правильно розташувати ваші погляди

  • autoresizingMask, який буде розміщувати кожен вид, до якого не додано жодних обмежень ( відзначимо, авторозмітка та обмеження поля тепер сумісні в тому ж представленні \ 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: Якщо у вас є краще рішення, не пишіть іншої відповіді. Я перенесу прийняту відповідь.


3
thx, але мені не вдалося знайти радар з ідентифікатором 28562874, чи можу я отримати URL-адресу радара?
Joey

Працював як шарм для мене, також для шарів поглядів!
hlynbech

@Joey, я не знаю, чи можу я отримати посилання моєї помилки. Прямої URL-адреси я не знайшов, і, схоже, інші користувачі не бачать моїх звітів. Відповідно до цієї SO-відповіді stackoverflow.com/a/145223/127493 , здається, найкращим способом підвищити пріоритет помилки є створення дубліката.
Мартін

З боку Apple це все нерозумно. У мене повністю визначений контролер автоматичного перегляду макета, і деякі текстові поля та UIView мають цей дурний 0,0,1000,1000 кадр. Але не всі. Як це може уникнути QA? Я думаю, я можу подати ще один радар, який вони не прочитають.
ahwulf

3
Я хотів би подякувати вам і всім іншим за вашу думку та пропозиції. Я витрачав цілий день, витягуючи волосся, тому що UIStackViewвнутрішня частина UICollectionViewCellне повертала правильного зросту протягом viewDidLayoutSubviews. Виклик layoutIfNeededнегайно усунув проблему.
Руїс

40

Чи використовуєте ви закруглені кути для своєї кнопки? Спробуйте зателефонувати layoutIfNeeded()раніше.


1
ага, намагаєтесь отримати більше представників? Як я вже сказав у своєму коментарі, це не відповідає на питання. Однак мені це допомогло, тому ви заробили +1 :)
Мартін

4
Це може допомогти комусь у майбутньому, і це легше помітити порівняно з коментарями
Eugen Dimboiu

1
Допоміг мені щойно!
daidai

@daidai радий це почути!
Eugen Dimboiu

це працює! але чому? також, але яке правильне рішення для отримання належного розміру кадру?
Crashalot

24

Рішення: Wrap все всередині viewDidLayoutSubviewsв DispatchQueue.main.async.

// swift 3

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

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

1
це працює, навіть якщо вкласти у -viewDidLoad... такий дивний обхідний шлях
medvedNick

1
Можливо тому, що коли viewDidLayoutSubviews система не мала часу зробити те, що потрібно було зробити. Виклик диспетчера змушує вас перестрибнути один пробіг, що дозволяє системі закінчити його слово. Якщо я маю рацію, коли ти міг отримати таку саму поведінку зі сном у кілька мілісекунд
тибот Ной

тому що всі завдання інтерфейсу потрібно виконувати в основній темі, дякую за ваше рішення!
danywarner

18

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

"Надіслати layoutIfNeeded до подання не очікується переміщення представлення даних, але в попередніх випусках, якщо представлення переводитьAutoresizingMaskIntoConstraints у значення" НІ ", і якщо воно розміщується обмеженнями, layoutIfNeeded перемістить подання, щоб відповідати механізму компонування перед відправленням макета Ці зміни виправляють цю поведінку, і розташування одержувача та, як правило, його розмір не впливатимуть на layoutIfNeeded.

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

Сторонні програми з користувацькими підкласами UIView з використанням автоматичного макета, які замінюють макет. Підпрогляди та брудний макет самостійно перед викликом супер ризикують викликати цикл зворотного зв'язку щодо макета під час відновлення на iOS 10. Коли вони правильно надішлють наступні виклики layoutSubviews, вони повинні бути впевнені, що перестаньте забруднювати макет самостійно в якийсь момент (зверніть увагу, що цей виклик був пропущений у випуску до iOS 10). "

По суті, ви не можете викликати layoutIfNeeded на дочірньому об'єкті View, якщо ви використовуєте translateAutoresizingMaskIntoConstraints - тепер виклик 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.


Використання меж mainScreen не є хорошим рішенням, оскільки в багатьох випадках viewController не займає весь екран. Крім того, вам слід зателефонувати [super viewDidLayoutSubviews];в цьому методі через багато речей з автоматичного розкладу, зроблених самим видом
Martin

@Martin, що ти рекомендуєш? Погоджено, це не здається ідеальним.
Crashalot

@Crashalot, як я кажу у своїй відповіді, використовуючи autolayout або autoresizingMask, правильно розташував би ваші UIViews. Але якщо вам потрібно виконати спеціальні обчислення на певному кадрі перегляду, відповідь Євгена працює: зателефонуйте layoutIfNeeded. У мене є відчуття, що це не найкраще рішення, але я все ще не знайшов кращого.
Мартін

2

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


Дякую за вашу відповідь. Документація щодо Apple viewDidLayoutSubviewsдосить неоднозначна. Друге речення в "дискусіях" певним чином суперечить останньому. developer.apple.com/reference/uikit/uiviewcontroller/…
Мартін

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

Отже, від Діда до Вілла

Супер дивно


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

Замініть макетSublayers (шару: CALayer) замість layoutSubviews в підрозділі комірок, щоб мати правильні кадри


0

Якщо вам потрібно зробити щось на основі кадру вашого перегляду - замініть макетSubviews та layout callIfNeeded

    override func layoutSubviews() {
    super.layoutSubviews()

    yourView.layoutIfNeeded()
    setGradientForYourView()
}

У мене виникла проблема з viewDidLayoutSubviews, що повертав неправильний кадр для мого перегляду, для чого мені потрібно було додати градієнт. І лише layoutIfNeeded вчинив правильно: :)


-1

Відповідно до нового оновлення в ios, це насправді помилка, але ми можемо зменшити це за допомогою -

Якщо ви використовуєте xib з авторозміткою у своєму проекті, вам доведеться просто оновити кадр у налаштуваннях авторозмітки, будь ласка, знайдіть для цього зображення.введіть тут опис зображення

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.