595просмотров
18 ноября 2025 г.
provocationScore: 655
Окей, бойлерплейта не осталось. Но ко всем проблемам прошлого решения добавились проблемы стилей. Теперь их никак не получится переопределить или как-то кастомизировать: MyLabel { MyTitle("Title") .font(.title) // Will be ignored. MySubtitle("Subtitle") .mySubtitleStyle(.customStyle) // Will be ignored.
} icon: { / ... / } А еще нужно следить, чтобы эти стили случайно не переопределили стили других вложенных компонентов. После двух неудачных подходов начинает складываться понимание, что в целом ожидается от решения, с которым будет удобно работать:
- Дефолтные настройки должны применяться самостоятельно.
- Область видимости этих настроек должна не выходить за пределы компонента.
- Есть возможность для переопределения настроек на любом уровне. Hierarchical View Builders Идея заключается в том, что мы можем делить наш контент на уровни по степени важности: primary, secondary, ternary... И таким образом можно будет естественным образом получить зоны для применения стилей: MyLabel { Text("Title") // Implicitly Primary
} icon: { Image(systemName: "star.fill") // Implicitly Primary
} MyLabel { Text("Title") Text("Subtitle") // ERROR: Implicit levels are available only for single view content
} icon: { Image(systemName: "star.fill")
} MyLabel { PrimaryContent { Text("Title") // Explicitly Primary } SecondaryContent{ Text("Subtitle") // Explicitly Secondary .foregroundStyle(.green) // Can modify the style }
} icon: { Image(systemName: "star.fill") .foregroundStyle(.red) // Can modify the style
} В простых кейсах клиентам не нужно ни о чем думать, а в сложных компилятор выдаст ошибку и потребует явно разбить контент на уровни. Сами стили могут быть применены на определенный уровень и скоупиться вокруг определенного контекста: struct MyLabel<Title: View, Icon: View>: View { @HierarchicalViewBuilder<MyLabelTitleContext> let title: Title @HierarchicalViewBuilder<MyLabelIconContext> let icon: Icon var body: some View { HStack { icon .hierarchicalStyle( IconStyle(), level: PrimaryContentLevel.self, predicate: MyLabelIconContext.self ) VStack(alignment: .leading) { title } .hierarchicalStyle( PrimaryTextStyle(), level: PrimaryContentLevel.self, predicate: MyLabelTitleContext.self ) .hierarchicalStyle( SecondaryTextStyle(), level: SecondaryContentLevel.self, predicate: MyLabelTitleContext.self ) } }
} MyLabelTitleContext здесь выступает как маркерный тип, который позволяет статически определить, к какой именно вью нужно применить стиль. А HierarchicalViewBuilder выполняет весь бойлерплейт, связанный с пробрасыванием этого контекста через иерархию. Если примеры выше вас заинтересовали, то вот полная версия этого кода. Забирайте себе и пишите крутые дизайн-системы! А в следующем посте подробнее расскажу про нюансы работы с @resultBuilder и написание своих DSL.