Я хочу создать многоразовый компонент Swiftui, который отображает сетку карт. Макет должен быть гибким в зависимости от предоставленных данных. сторона.
Прямо сейчас я сделал это. Но должен быть лучший способ сделать это. struct SettingsCardView: View {
@ObservedObject var viewModel: SettingsCardItemViewModel
var body: some View {
BindableView(viewModel: viewModel) {
makeContent()
}
}
@ViewBuilder
private func makeContent() -> some View {
Button {
viewModel.onClick()
} label: {
VStack(alignment: .leading) {
TextView(viewModel.title)
TextView(viewModel.subtitle)
Spacer()
HStack {
Spacer()
makeIcon()
}
}
.modifier(SettingsCard())
}
}
@ViewBuilder
private func makeIcon() -> some View {
switch viewModel {
case let battery as BatterySettingsCardViewModel:
BatteryIcon(viewModel: battery.batteryIconViewModel)
case let greenLight as GreenStatusSettingsCardViewModel:
DefaultSettingsIcon(viewModel: greenLight.icon)
case let connectionNotification as ConnectionNotificationSettingsCardViewModel:
DefaultSettingsIcon(viewModel: connectionNotification.icon)
default:
EmptyView()
}
}
}
public class SettingsCardItemViewModel: BindableViewModel, Identifiable {
public var title: TextViewModel
public var subtitle: TextViewModel
public init(
title: String,
subtitle: String,
subtitleTextViewModel: TextViewModel? = nil
) {
self.title = .b1(title, color: .uiTextLightPrimary)
self.subtitle = subtitleTextViewModel ?? .b1(subtitle, color: .uiTextLightPrimaryInactive)
}
public func onClick() {
Debugger.fail(desc: "onClick not implemented")
}
}
public class BatterySettingsCardViewModel: SettingsCardItemViewModel {
public let batteryIconViewModel: BatteryIconViewModel
public init() {
self.batteryIconViewModel = BatteryIconViewModel()
super.init(title: "Battery", subtitle: "", subtitleTextViewModel: .h0("92%", color: .uiTextLightSecondary))
}
}
public class GreenStatusSettingsCardViewModel: SettingsCardItemViewModel {
public var icon: DefaultSettingsIconViewModel
public init() {
self.icon = DefaultSettingsIconViewModel(icon: .iconArrowLeft(.medium))
super.init(title: "Green status", subtitle: "Inactive")
}
public override func onClick() { }
}
public class ConnectionNotificationSettingsCardViewModel: SettingsCardItemViewModel {
public var icon: DefaultSettingsIconViewModel
public init() {
self.icon = DefaultSettingsIconViewModel(icon: .iconArrowLeft(.medium))
super.init(title: "Connection notifications", subtitle: "Inactive")
}
public override func onClick() { }
}
public class BatteryIconViewModel: BindableViewModel {
public override init() { }
}
public class BaseSettingsMenuSection: Identifiable, ObservableObject {
public var title: TextViewModel?
public var subtitle: TextViewModel?
@Published public var items: [SettingsCardItemViewModel]
public init(title: String? = nil, subtitle: String? = nil, items: [SettingsCardItemViewModel]) {
if let title {
self.title = .h2(title)
}
if let subtitle {
self.subtitle = .b1(subtitle, color: .uiTextLightSecondary)
}
self.items = items
}
}
public class OneColumn: BaseSettingsMenuSection { }
public class TwoColumn: BaseSettingsMenuSection { }
public class CameraSettingsCardViewModel: SettingsCardItemViewModel {
public var icon: DefaultSettingsIconViewModel
public init() {
self.icon = DefaultSettingsIconViewModel(icon: .iconSpeedCam(.medium))
super.init(title: "Cameras", subtitle: "Active")
}
}
public class RoadHazardSettingsCardViewModel: SettingsCardItemViewModel {
public var icon: DefaultSettingsIconViewModel
public init() {
self.icon = DefaultSettingsIconViewModel(icon: .iconRoadHazad(.medium))
super.init(title: "Road Hazard", subtitle: "InActive")
}
}
public class SpeedLimitSettingsCardViewModel: SettingsCardItemViewModel {
public var icon: DefaultSettingsIconViewModel
public init() {
self.icon = DefaultSettingsIconViewModel(icon: .iconSpeedCam(.medium))
super.init(title: "Speed Limit", subtitle: "Active")
}
}
public class NotificationSettingsCardViewModel: SettingsCardItemViewModel {
public var icon: DefaultSettingsIconViewModel
public init() {
self.icon = DefaultSettingsIconViewModel(icon: .iconNotificationOn(.medium))
super.init(title: "Notifications", subtitle: "Active")
}
}
struct SettingsMenuSectionView: View {
@ObservedObject var viewModel: BaseSettingsMenuSection
var body: some View {
VStack(alignment: .leading) {
VStack(alignment: .leading, spacing: 16) {
if let title = viewModel.title {
TextView(title)
}
if let subtitle = viewModel.subtitle {
TextView(subtitle)
}
}
makeItemList()
}
}
@ViewBuilder
private func makeItemList() -> some View {
switch viewModel {
case is OneColumn:
makeOneColumnItemList()
case is TwoColumn:
makeTwoColumnItemList()
default:
EmptyView()
}
}
@ViewBuilder
private func makeOneColumnItemList() -> some View {
VStack(spacing: Constants.Padding.padding8) {
ForEach(viewModel.items) { item in
SettingsCardView(viewModel: item)
}
}
}
@ViewBuilder
private func makeTwoColumnItemList() -> some View {
HStack(spacing: Constants.Padding.padding8) {
ForEach(viewModel.items) { item in
SettingsCardView(viewModel: item)
}
}
}
}
public class BaseSettingsMenuSection: Identifiable, ObservableObject {
public var title: TextViewModel?
public var subtitle: TextViewModel?
@Published public var items: [SettingsCardItemViewModel]
public init(title: String? = nil, subtitle: String? = nil, items: [SettingsCardItemViewModel]) {
if let title {
self.title = .h2(title)
}
if let subtitle {
self.subtitle = .b1(subtitle, color: .uiTextLightSecondary)
}
self.items = items
}
}
public class OneColumn: BaseSettingsMenuSection { }
public class TwoColumn: BaseSettingsMenuSection { }
Подробнее здесь: https://stackoverflow.com/questions/797 ... rid-layout