Если зайти в детали заказа без интернета и выйти (свайпом), то индикатор загрузки будет поверх списка блюд
Криво работает переход из портретного режима в альбомный (текста заезжают за safeArea, галерея изображений ломается и т.д.)
Нет иконки приложения (минор)
Нет экрана загрузки (минор)
Замечания по коду (общие):
associatedtype model - наименование типов всегда пишется с большой буквы
Lazy св-ва лучше делать по необходимости. Настройку каждого subview лучше вынести в отдельные методы, для удобочитаемости и что бы св-ва не занимали столько места.
Пройтись по всем функциям и проставить модификаторы доступа, где это необходимо
Для удобочитаемости можно разбивать класс используя MARK на св-ва/жц/приватные методы/публичные методы
Есть утечки памяти, нужно проверить все замыкания и связи на retain cycle
Модели должны храниться каждая в своем файле (наименование класса = название файла). Исключением может быть protocol + базовый класс и то я бы советовал тоже разделять в разные файлы.
RecipesViewController:
func didUpdateViewModel функции должны указывать действие без вспомогательных глаголов (действие еще не совершено) updateViewModel
let h = hud имена функций и переменных всегда должны быть осмысленными, даже в рамках небольшого контекста (h не приемлемо)
navigationController?.navigationBar.prefersLargeTitles = true - navigationController не должен настраиваться из UIViewController, это противоречит принципам solid
viewModel?.filteredRecipes.count ?? 0 и 112 + 26 если функция что-то возвращает, лучше явно указывать ключевым словом return (исключение только inline замыкания)
private extension RecipesViewController с публичными методами может запутать того, кто будет проверять код. В рамках одного метода не понятно является он публичным или нет, плюс расширением эти функции не являются, это базовые функции класса и выносить их в extension нет необходимости (нарушается сама идея этого ключевого слова)
HudView(inView: self.navigationController?.view ?? self.view, type: type) если нужно отобразить hud на весь экран, лучше делегировать эту ответственность router’у, который в явном виде знает о NavigationController
RecipesViewModel:
didHud из названия не совсем ясно что нужно сделать (didRequestShowHUD)?
RecipesViewModelProtocol - св-ва и функции перемешаны. Сначала должны быть описаны все св-ва, затем все функции
let router: RouterProtocol - тут скорее всего retain cycle. Не уверен, что это хорошая идея напрямую инжектить роутер во viewModel, т.к. для viewModel открыты методы, о которых ей знать не надо. Лучше воспользоваться партерном Delegate
var isFiltering: Bool = false - нигде не используется
updateSearchResults(searchController: UISearchController) - viewModel не должна знать ничего о View. Передавать нужно не UI компоненты, а данные простого типа или модели (в данном случае, это text и index)
filter({…}) если замыкание является последним параметром и оно в единственном числе (больше замыканий нету), то можно опустить скобки и явное наименование параметра (trailing closure)
case 0: - лучше осмыслить числа именованными константами (или кастомным енамом), это сделает код более читаемым (case Constants.nameFilterIndex)
RecipeTableViewCell:
Цвета и шрифты надо вынести в отдельный класс констант или расширение к UIColor/UIFont (это касается всего приложения). Если шрифт нативный, нужно завернуть его в кастомное название, что бы при смене шрифта достаточно было изменить его в одном месте
// let tap - закомментированного кода не должно быть (если не нужен лучше удалить, всегда можно восстановить по истории в гите)
Аналогичные замечания про расширения, наименование функций и переменных
DispatchQueue.main.async думаю, лучше заворачивать обработку результата внутри ViewModel, что бы не делать это для каждого замыкания снаружи
DetailRecipeViewController имеет публичный extension с настройкой элементов, который выглядит довольно громоздко. Тут надо избавиться от него двумя шагами - 1. Перенести функции в класс и сделать их приватными. 2. Уменьшить кол-во subview путем разделения UI на несколько блоков (отдельными View-контейнерами)
PhotoViewModel:
Работу с PHPhotoLibrary стоит вынести и завернуть в отдельный класс
HudView:
Судя по всему это не только HUD, но еще и экран отображения ошибок. Стоит разделить эти экраны и отображать индикатор загрузки непосредственно на экране, а ошибку уже исходя из дизайна (если это фуллскрин)
NetworkService:
Можно закрыть сервис протоколами с методами для работы с конкретными моделями (например getRecipes() -> Recipes) и вызывать уже их.
Inte+getDate:
Лучше не делать такие расширения, т.к. не из всех чисел можно получить дату и не везде этот функционал нужен. Использовать форматирование лучше по месту
String+replace:
Не понятен смысл этого расширения. Внутри используется тоже самое, что можно сделать снаружи. Плюс не очень хорошо делать дефолтными значениями значения под конкретные случаи (“ ” и “\n”)
NSString.CompareOptions.literal -> можно просто .literal
На остальных экранах внести исправления опираясь на вышеописанные
Есть проблемы с наименованием функций, надо стараться подбирать такое название, что бы оно четко отображало то, что происходит в теле функции
RecipesViewController:
func bindUpdateViewModel звучит странно, лучше назвать bindToViewModel
updateTableView, showHUD, showLoader названия функций не отражают их реальное назначение, на деле в них биндинг, который, по идее, можно весь оставить в корневой функции bindToViewModel
112 + 26 зачем эта сумма? Если эти значения что-то обозначаю, то нужно вынести их в константы и дать текстовое обозначение. Иначе лучше просто делать return 138
HudView(inView: self.view, type: type) все таки не очень хорошо, что показом HUDа занимается он сам, эта ответственность должна быть на том, кто его показывает (откуда его надо показать):
let hud - HudView(networkType: error)
self.addSubview(hud)
...
Вообще можно вынести показ HUDа отдельным протоколом и базовой реализацией, а для конкретных экранов уже подписываться этим протоколом и вызывать базовую реализацию (если не понятно скину пример)
RecipesViewModel:
let router: RouterProtocol - тут скорее всего retain cycle. Не уверен, что это хорошая идея напрямую инжектить роутер во viewModel, т.к. для viewModel открыты методы, о которых ей знать не надо. Лучше воспользоваться партерном Delegate (этот пункт стоит посмотреть)
RecipeTableViewCell:
тут тоже стоит переделать хранение и настройку свойств (subviews которые)
HudView:
Тут все таки надо подумать о разделении показа индикатора от ошибки или переименовать View, что бы было понятно что оно отображает