Code Review Asked on October 27, 2021
Edit: I added another configuration vector to CardView, a size
attribute, which may be .small
or .medium
. As such, I had to refactor the design to create several descendent protocols of CardView
for each attribute, and several concrete structs corresponding to each possible combinations of size and style. Here is the updated design:
// MARK: Example
let example: some View = CardView2Factory.create(
colors: (.red, .blue),
name: "My Birthday",
range: .init(start: Date(), duration: 0),
configuration: (size: .small, style: .inverted)
)
// MARK: Factory
/// A Factory class used to create a customized `CardView`
class CardView2Factory {
enum Size {
case small, medium
}
enum Style {
case plain, inverted
}
private init() {}
/// Creates a new CardView.
///
/// The unique combination of the configuration's `size` and `style` attributes correspond one-to-one with an internal View struct conforming to `CardView`
///
/// - Parameters:
/// - colors: the tuple of colors of an Event, used to form the stops of the gradient
/// - name: the name of an Event
/// - range: the range of an Event
/// - configuration: a configuration specifying the size and style attributes of the View
///
/// - Returns: A configuration-dependent CardView expressed as a View
@ViewBuilder
static func create(colors: (Color, Color), name: String, range: DateInterval, configuration: (size: Size, style: Style)) -> some View {
switch configuration {
case (.small, .plain):
SmallPlainCardView2(colors: colors, name: name, range: range)
case (.small, .inverted):
SmallInvertedCardView2(colors: colors, name: name, range: range)
case (.medium, .plain):
MediumPlainCardView2(colors: colors, name: name, range: range)
case (.medium, .inverted):
MediumInvertedCardView2(colors: colors, name: name, range: range)
}
}
}
// MARK: Main Protocol
protocol CardView2: View {
associatedtype Accent: ShapeStyle & View
associatedtype Background: ShapeStyle
var colors: (Color, Color) { get }
var name: String { get }
var range: DateInterval { get }
var headerTextColor: Color { get }
var accent: Accent { get }
var background: Background { get }
}
// MARK: Size Protocols
extension CardView2 {
fileprivate var dateFormatter: DateFormatter {
}
fileprivate var dateComponentsFormatter: DateComponentsFormatter {
}
fileprivate var mainText: Text {
}
fileprivate var shape: some Shape {
}
fileprivate var text: some View {
}
}
protocol SmallCardView2: CardView2 {
}
extension SmallCardView2 {
var body: some View {
ViewA(colors, name, range, accent, etc...)
}
}
protocol MediumCardView2: CardView2 {
}
extension MediumCardView2 {
var body: some View {
ViewB(colors, name, range, accent, etc...)
}
}
// MARK: Style Protocols
protocol PlainCardView2: CardView2 {
}
extension PlainCardView2 {
var headerTextColor: Color {
}
var accent: LinearGradient {
}
var background: Color {
}
}
protocol InvertedCardView2: CardView2 {
}
extension InvertedCardView2 {
var headerTextColor: Color {
}
var accent: Color {
}
var background: LinearGradient {
}
}
// MARK: Implementation
fileprivate struct SmallPlainCardView2: SmallCardView2, PlainCardView2 {
let colors: (Color, Color)
let name: String
let range: DateInterval
}
fileprivate struct SmallInvertedCardView2: SmallCardView2, InvertedCardView2 {
let colors: (Color, Color)
let name: String
let range: DateInterval
}
fileprivate struct MediumPlainCardView2: MediumCardView2, PlainCardView2 {
let colors: (Color, Color)
let name: String
let range: DateInterval
}
fileprivate struct MediumInvertedCardView2: MediumCardView2, InvertedCardView2 {
let colors: (Color, Color)
let name: String
let range: DateInterval
}
and here is the corresponding class diagram (albeit in Java):
Using SwiftUI, I have multiple different Views which only differ in some style-based variables. So, I created a Protocol CardView
to condense all the similarities in one entity. The Protocol itself inherits from View
and implements most of the functionality including var body
. It also has several associatedtype
s.
I want the client to be able to retrieve an instance of a struct that conforms to this Protocol by simply selecting a case from an enum Style
. I decided to use the Factory pattern to do so. However, I learned that Protocols can’t have static functions themselves so I had to create a new CardViewFactory
class that had a static create
method with a style: Style
parameter. Another concession I had to make was that this factory method returned some View
and not a CardView
as would have been preferred, due to its associatedtype
requirements.
I’m looking for any feedback on the design of my hierarchy and implementation of the Factory pattern. Thanks!
CardViewFactory
class CardViewFactory {
enum Style {
case plain, inverted
}
@ViewBuilder static func create(name: String, range: DateInterval, colors: (Color, Color), style: Style) -> some View {
switch style {
case .plain:
CardViewA(colors: colors, name: name, range: range)
case .inverted:
CardViewB(colors: colors, name: name, range: range)
}
}
}
CardView
protocol CardView: View {
associatedtype Accent: ShapeStyle & View
associatedtype Background: ShapeStyle
var colors: (Color, Color) { get }
var name: String { get }
var range: DateInterval { get }
var headerTextColor: Color { get }
var accent: Accent { get }
var background: Background { get }
}
extension CardView {
var body: some View {
EmptyView() // for example
}
}
Example Implementation
struct CardViewA: CardView {
let colors: (Color, Color)
let name: String
let range: DateInterval
let headerTextColor: Color = .black
var accent: LinearGradient {
LinearGradient(
gradient: .init(colors: [colors.0, colors.1]),
startPoint: .topTrailing,
endPoint: .bottomLeading
)
}
let background: Color = .white
}
Get help from others!
Recent Questions
Recent Answers
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP