I need to construct a reusable SwiftUI element that shows a grid of playing cards. The format needs to be versatile relying on the info offered.
For instance, generally the primary row ought to include two smaller playing cards aspect by aspect, and the second row ought to include a big card spanning each columns (like within the screenshot under):
Different occasions, the format is likely to be inverted — the primary row containing a single massive card spanning two columns, and the second row containing two smaller playing cards aspect by aspect.
Proper now I’ve made it like this. However there should be a greater technique to do it.
So what’s one of the simplest ways to construction this in SwiftUI so it may be used as a reusable element?
struct SettingsCardView: View {
@ObservedObject var viewModel: SettingsCardItemViewModel
var physique: some View {
BindableView(viewModel: viewModel) {
makeContent()
}
}
@ViewBuilder
personal func makeContent() -> some View {
Button {
viewModel.onClick()
} label: {
VStack(alignment: .main) {
TextView(viewModel.title)
TextView(viewModel.subtitle)
Spacer()
HStack {
Spacer()
makeIcon()
}
}
.modifier(SettingsCard())
}
}
@ViewBuilder
personal func makeIcon() -> some View {
change 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, colour: .uiTextLightPrimary)
self.subtitle = subtitleTextViewModel ?? .b1(subtitle, colour: .uiTextLightPrimaryInactive)
}
public func onClick() {
Debugger.fail(desc: "onClick not applied")
}
}
public class BatterySettingsCardViewModel: SettingsCardItemViewModel {
public let batteryIconViewModel: BatteryIconViewModel
public init() {
self.batteryIconViewModel = BatteryIconViewModel()
tremendous.init(title: "Battery", subtitle: "", subtitleTextViewModel: .h0("92%", colour: .uiTextLightSecondary))
}
}
public class GreenStatusSettingsCardViewModel: SettingsCardItemViewModel {
public var icon: DefaultSettingsIconViewModel
public init() {
self.icon = DefaultSettingsIconViewModel(icon: .iconArrowLeft(.medium))
tremendous.init(title: "Inexperienced standing", subtitle: "Inactive")
}
public override func onClick() { }
}
public class ConnectionNotificationSettingsCardViewModel: SettingsCardItemViewModel {
public var icon: DefaultSettingsIconViewModel
public init() {
self.icon = DefaultSettingsIconViewModel(icon: .iconArrowLeft(.medium))
tremendous.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?
@Printed public var gadgets: [SettingsCardItemViewModel]
public init(title: String? = nil, subtitle: String? = nil, gadgets: [SettingsCardItemViewModel]) {
if let title {
self.title = .h2(title)
}
if let subtitle {
self.subtitle = .b1(subtitle, colour: .uiTextLightSecondary)
}
self.gadgets = gadgets
}
}
public class OneColumn: BaseSettingsMenuSection { }
public class TwoColumn: BaseSettingsMenuSection { }
public class CameraSettingsCardViewModel: SettingsCardItemViewModel {
public var icon: DefaultSettingsIconViewModel
public init() {
self.icon = DefaultSettingsIconViewModel(icon: .iconSpeedCam(.medium))
tremendous.init(title: "Cameras", subtitle: "Lively")
}
}
public class RoadHazardSettingsCardViewModel: SettingsCardItemViewModel {
public var icon: DefaultSettingsIconViewModel
public init() {
self.icon = DefaultSettingsIconViewModel(icon: .iconRoadHazad(.medium))
tremendous.init(title: "Highway Hazard", subtitle: "InActive")
}
}
public class SpeedLimitSettingsCardViewModel: SettingsCardItemViewModel {
public var icon: DefaultSettingsIconViewModel
public init() {
self.icon = DefaultSettingsIconViewModel(icon: .iconSpeedCam(.medium))
tremendous.init(title: "Velocity Restrict", subtitle: "Lively")
}
}
public class NotificationSettingsCardViewModel: SettingsCardItemViewModel {
public var icon: DefaultSettingsIconViewModel
public init() {
self.icon = DefaultSettingsIconViewModel(icon: .iconNotificationOn(.medium))
tremendous.init(title: "Notifications", subtitle: "Lively")
}
}
struct SettingsMenuSectionView: View {
@ObservedObject var viewModel: BaseSettingsMenuSection
var physique: some View {
VStack(alignment: .main) {
VStack(alignment: .main, spacing: 16) {
if let title = viewModel.title {
TextView(title)
}
if let subtitle = viewModel.subtitle {
TextView(subtitle)
}
}
makeItemList()
}
}
@ViewBuilder
personal func makeItemList() -> some View {
change viewModel {
case is OneColumn:
makeOneColumnItemList()
case is TwoColumn:
makeTwoColumnItemList()
default:
EmptyView()
}
}
@ViewBuilder
personal func makeOneColumnItemList() -> some View {
VStack(spacing: Constants.Padding.padding8) {
ForEach(viewModel.gadgets) { merchandise in
SettingsCardView(viewModel: merchandise)
}
}
}
@ViewBuilder
personal func makeTwoColumnItemList() -> some View {
HStack(spacing: Constants.Padding.padding8) {
ForEach(viewModel.gadgets) { merchandise in
SettingsCardView(viewModel: merchandise)
}
}
}
}
public class BaseSettingsMenuSection: Identifiable, ObservableObject {
public var title: TextViewModel?
public var subtitle: TextViewModel?
@Printed public var gadgets: [SettingsCardItemViewModel]
public init(title: String? = nil, subtitle: String? = nil, gadgets: [SettingsCardItemViewModel]) {
if let title {
self.title = .h2(title)
}
if let subtitle {
self.subtitle = .b1(subtitle, colour: .uiTextLightSecondary)
}
self.gadgets = gadgets
}
}
public class OneColumn: BaseSettingsMenuSection { }
public class TwoColumn: BaseSettingsMenuSection { }