Pull to refresh
14
0

iOS-разработчик

Send message

Спасибо, что поделились! Интересно, как часто с таким подходом Вы встречаетесь с ложноположительными срабатываниями? Например, из-за того что SwiftUI может потерять ссылку на StateObject или создать его несколько раз?

Мы начали примерно с того же кода с форсированием лейаутов, но это было слишком медленно.

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

А позже пришли к более изящному и быстрому решению – модификации текущей транзакции на лету.

При помощи модификатора View.transaction(:) мы выключаем анимации для всех view в иерархии в нужные моменты времени. Поэтому решение и работает максимально быстро.

extension View {
  func disabledAnimations(_ disabled: @escaping () -> Bool) -> some View {
    self.transaction { (transaction: inout Transaction) in
      if disabled() {
        transaction.disablesAnimations = true
      }
    }
  }
}

Спасибо за вопросы, постараюсь ответить на все по порядку:

Не соглашусь, отсутствие выноса расчётов layout в фон — это особенность не декларативного подхода в целом, а одной из его реализаций (SwiftUI).

Ага, я это и имел ввиду) Что в декларативной парадигме у тебя нет возможности повлиять на поведение системных компонентов. Как Apple их реализовали, так они и будут работать.

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


А про размеры в фоне, это просто пример из головы. У нас и правда в лейаутах - нечего рассчитывать в фоне.

Хочется уточить, что это не особенность поведения SwiftUI, а норма для UIKit

Да, действительно UIKit и SwiftUI оба привязаны к транзакциями. Но здесь есть одно отличие, если UIKit только лейаутится (рендерится и т.д), то SwiftUI производит полный пересчет View.body у тех View, чей стейт менялся внутри CATransaction. При этом применит анимации между начальным значением стейта и последним, пропустив все промежуточные.

Из-за чего и происходит баг с ненужными анимациями. Если на примере, то последовательность примерно такая:

  • CATransaction.begin();

  • У ячейки был контент какого-то файла;

  • Мы заменили контент ячейки на плейсхолдер;

  • Затем обновили контент ячейки до актуального состояния нового файла;

  • CATransaction.flush();

  • Произошли анимации между контентом старого и нового файла. А плейсхолдер был проигнорирован как промежуточный.

И даже если мы напишем что-то в духе:

var transaction = Transaction()
transaction.disablesAnimations = true

withTransaction(transaction) {
  hostingController.rootView = /* New File Content */
}

То ничего не сработает, потому что в случае с UIHostingController апдейт все еще будет произведен в конце ранлупа когда выполнится CATransaction.flush(), а наша Transaction будет проигнорирована.

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

Но вообще safe area для него и правда больная тема, и если она не нужна, то ее можно отключить:

if #available(iOS 16.4, *) {
  hostingController.safeAreaRegions = []
} else {
  hostingController._disableSafeArea = true
}

Скорее принятый подход. У всех решений есть свои плюсы и минусы. 

Если бы мы остались на UIKit, то не нужно было бы сильно ухищряться с поддержкой старых версий, но при этом верстать красивый и качественный UI было бы гораздо сложнее. Для нас действительно важен хороший, стабильный UI. 

Со SwiftUI это делать куда проще, но и здесь есть свои недостатки, главный из которых это более трудоемкая поддержка старых версий iOS.

Спасибо за фидбэк, я понял, что не сразу считывается, что перфоманс улучшился и на других версиях iOS, дополню это в тексте.

было принято решение внедрить визуальные улучшения на «новом фреймворке» в эту устаревшую версию ios и именно это решение ускорили в «3 раза».

Не совсем, если мы хотим сделать решение на новом фреймворке, то не можем просто так взять и не поддержать его одинаково хорошо на всех версиях iOS которые у нас есть. 

А стали работать быстрее мы независимо от версии iOS, в частности, все тесты проводились на iPhone 15 Pro Max и iOS 17.4.1.

Кто-то пишет не использовать параметр cornerRadius. Применение viewLayer.cornerRadius может привести к offscreen rendering. Вместо этого можно использовать класс UIBezierPath

Судя по видео из источников (https://developer.apple.com/videos/play/tech-talks/10857/), использование .cornerRadius приводит к offscreen rendering, только если .masksToBounds == true, но это все равно эффективнее чем использовать маску.

Information

Rating
Does not participate
Registered
Activity