{"id":29460405,"url":"https://github.com/saverio-negro/modalcard","last_synced_at":"2025-07-14T02:04:14.343Z","repository":{"id":300446564,"uuid":"1006112589","full_name":"saverio-negro/ModalCard","owner":"saverio-negro","description":"`ModalCard` is a a slot-based and adaptive-layout view. It's a SwiftUI custom framework that reproduces the system design similar to Apple's `Alert` and many other native components.","archived":false,"fork":false,"pushed_at":"2025-07-12T22:18:55.000Z","size":474,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-07-13T00:21:32.997Z","etag":null,"topics":["apple","framework","swift","swiftui"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/saverio-negro.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-06-21T14:19:59.000Z","updated_at":"2025-07-12T22:18:58.000Z","dependencies_parsed_at":"2025-07-13T00:24:19.549Z","dependency_job_id":null,"html_url":"https://github.com/saverio-negro/ModalCard","commit_stats":null,"previous_names":["saverio-negro/modalcard"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/saverio-negro/ModalCard","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saverio-negro%2FModalCard","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saverio-negro%2FModalCard/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saverio-negro%2FModalCard/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saverio-negro%2FModalCard/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/saverio-negro","download_url":"https://codeload.github.com/saverio-negro/ModalCard/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/saverio-negro%2FModalCard/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265233505,"owners_count":23731800,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["apple","framework","swift","swiftui"],"created_at":"2025-07-14T02:02:00.920Z","updated_at":"2025-07-14T02:04:14.320Z","avatar_url":"https://github.com/saverio-negro.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 03 - Slot-Based and Adaptive Layouts with `ModalCard` view\n\n\u003cimg src=\"./ModalCard.gif\" width=\"25%\"/\u003e\n\n## Introduction\n\nThe `ModalCard` view component is meant to be a _reusable_ and _configurable_ modal by having it accept configurable parameters, both variables (state) and functions (behaviors); hence, an _adaptive_ layout that can be reused across apps.\n\nConcepts being used in the `ModalCard` component build on top of the `Card` view component. Therefore, if you'd like to have a look at the `Card` view component before checking the `ModalCard`, head over to the \u003ca href=\"https://github.com/saverio-negro/Card\"\u003eCard View Component\u003c/a\u003e Github project. It's quite a simple component, but you can take it as a groundwork for the `ModalCard` as well as more advanced components/frameworks I have coded.\n\n## Component Description\n\nAs described above, the component is a reusable modal card with title, message, and customizable action slots.\n\nWhile it's true that you can create your modal component depending on your purpose, my modal card is meant to better present confirmation dialogues or alerts with a customizable title and message, based on the specific scenario, as well as specific actions to perform upon confirmation or cancellation. However, this component shall serve you as a reference frame as far as how you will go about designing _your_ components based on the user experience needs.\n\nWith that out of the way, let's see how I implemented it, and why I came up with that specific solution. I'll walk you through the design-thinking process, and why this solution is _encapsulating_ and _scalable_.\nAlso, this is pretty _similar_ to the approach that Apple uses.\n\n**Note**: Take into account the fact that we can't see the actual implementation used by Apple since SwiftUI is a **closed-source** framework. However, I'm using reverse engineering by inspecting objects at runtime using Xcode's LLDB, inferring the behavior by checking out Apple's documentation, as well as referencing Apple's public APIs. What I think is important is that, after having gone through this README, you may appreciate the design-thinking approach, even to building a simple API as a `ModalCard`, which is meant to lay the foundation for more complex SwiftUI architecture patterns.\n\n### First Implementation Layer of `ModalCard`\n\nBefore getting straight to the final implementation of `ModalCard`, I would like to start presenting you the first layer of my implementation, which starts being very general and generic. As I go about explaining it, I'll outline the pros and cons, and what would a solution be in terms of code design to face those cons.\n\nThe following is the first layer of my implementation for a reusable `ModalCard` view that uses a **slot-based** system (configurable content-slots/parameters) to accept a title, message, as well as actions to perform upon confirmation or cancellation:\n\n```swift\npublic struct ModalCard\u003cPrimary: View, Secondary: View\u003e: View {\n\n  // MARK: - Properties\n\n  let title: String\n  let message: String\n  let primaryAction: Primary\n  let secondaryAction: Secondary\n\n  // MARK: - Init\n  \n  public init(\n    title: String,\n    message: String,\n    @ViewBuilder primaryAction: () -\u003e Primary,\n    @ViewBuilder secondaryAction: () -\u003e Secondary\n  ) {\n    self.title = title\n    self.message = message\n    self.primaryAction = primaryAction()\n    self.secondaryAction = secondaryAction()\n  }\n\n  // MARK: - Body\n\n  public var body: some View {\n      VStack(spacing: 15) {\n          Text(title)\n            .font(.headline)\n            .foregroundStyle(.primary)\n\n          Text(message)\n            .font(.subheadline)\n            .multilineTextAlignment(.center)\n            .foregroundStyle(.secondary)\n          \n          HStack(spacing: 15) {\n              secondaryAction\n              primaryAction\n          }\n          .padding()\n    }\n    .padding()\n    .background(\n      RoundedRectangle(cornerRadius: 20, style: .continuous)\n        .fill(Color.white)\n        .shadow(color: Color.black.opacity(0.1), radius: 5, x: 0, y: 2)\n    )\n    .padding()\n  }\n}\n```\n\nThat's a mouthful, isn't it? Well, let's go through it:\n\n```swift\npublic struct ModalCard\u003cPrimary: View, Secondary: View\u003e: View {\n```\n\n1. The code snippet above uses the `public` access modifier so that users using our component from _outside_ the `ModalCard` module can tap into it. This is crucial to any reusable API you are building.\n\n2. We use **parametric polymorphism** — simply known as **generics** — with our `ModalCard`, and all it does is define two _type parameters_ — `Primary` and `Secondary` — that we want this struct to work with. Essentially, these type parameters could be of any type, as long as they conform to the `View` protocol; in other words, they should be views. This is a powerful tool of Swift and many other programming languages out there (e.g., template classes in C++), which allows the code of our struct to work with any type, and those `Primary` and `Secondary` are two placeholders with the `View` type constraint applied to them `\u003cPrimary: View, Secondary: View\u003e`. However, why would we need it? That's because the `ModalCard` view component needs to declare two slots (properties) — our Primary and Secondary views, which are meant to be actions — so that the user of the component can decide what goes in there. In Object-Oriented Programming (OOP), this is also called **Object Composition**, and generics just allows us to define the type of objects — `View` objects (`Primary` and `Secondary`), in our case — with which to compose our `ModalCard` struct. In this case, because we are defining the slots of our modal card (e.g., title, message, etc.), we can also name this procedure as **slot-based view composition**, since we are defining two additional properties (slots) on the `ModalCard` struct to be two objects `primaryAction` and `secondaryAction` of type `Primary` and `Secondary`, both conforming to the `View` protocol.\n\n3. Finally, the `ModalCard` struct itself conforms to the `View` protocol, since it's meant to be a `View` that can be rendered from within the `body` property of any other `View` object.\n\nNext up,\n\n```swift\n// MARK: - Properties\n\n  let title: String\n  let message: String\n  let primaryAction: Primary\n  let secondaryAction: Secondary\n```\n\nIn the code above, we define our properties, which are the slots of our `ModalCard` object in which the user can pass its content, and reuse and adapt it based on their needs.\nThat includes the `Primary` and `Secondary` views, which are meant to be primary and secondary actions; more specifically, buttons that the user can define their behavior for, and pass to the modal card.\n\nLet's have a look at the next code snippet and see how we allow the user to pass over this information with our custom initializer:\n\n```swift\npublic init(\n    title: String,\n    message: String,\n    @ViewBuilder primaryAction: () -\u003e Primary,\n    @ViewBuilder secondaryAction: () -\u003e Secondary\n  ) {\n    self.title = title\n    self.message = message\n    self.primaryAction = primaryAction()\n    self.secondaryAction = secondaryAction()\n  }\n```\n\nWe are essentially defining four parameters for the `ModalCard` constructor/initializer. However, the ones we are most interested in are the `primaryAction` and `secondaryAction` parameters. Their type annotation entails a function that doesn't take any parameter and returns either a `Primary` or `Secondary` type. That will allow the user to pass any function under `primaryAction` and `secondaryAction` parameters — usually in the form of a closure — which allows for reusability and adaptability: they can return any object of type `View` from the closure, even multiple child views, and the `@ViewBuilder` property wrapper will bundle those views into a `TupleView` object. \n\n**Note**: I explain the `@ViewBuilder` property wrapper in the last component; namely, the \u003ca href=\"https://github.com/saverio-negro/Card\"\u003eCard View Component\u003c/a\u003e. Make sure to check it out!\n\nIn the code block relative to the constructor, we assign to `primaryAction` and `secondaryAction` properties the views being returned by the user-defined functions — which are passed to `ModalCard` upon its instantiation; in fact, notice that we are calling them — `primaryAction()` and `secondaryAction()` — since the initializer is passed a reference of those functions in memory, and to run their associated code blocks, we need to invoke them, which entails appending a set of parentheses; only then will we make sure that the properties are assigned the actual `View` objects, and not the functions themselves.\n\nFinally, we have the following code:\n\n```swift\npublic var body: some View {\n      VStack(spacing: 15) {\n          Text(title)\n            .font(.headline)\n            .foregroundStyle(.primary)\n\n          Text(message)\n            .font(.subheadline)\n            .multilineTextAlignment(.center)\n            .foregroundStyle(.secondary)\n          \n          HStack(spacing: 15) {\n              secondaryAction\n              primaryAction\n          }\n          .padding()\n    }\n    .padding()\n    .background(\n      RoundedRectangle(cornerRadius: 20, style: .continuous)\n        .fill(Color.white)\n        .shadow(color: Color.black.opacity(0.1), radius: 5, x: 0, y: 2)\n    )\n    .padding()\n  }\n```\n\nNow, I assume that, if you have come here to know a bit more about the SwiftUI architecture and how to build your own SwiftUI APIs, you must have some groundwork of basic SwiftUI. That means I won't go with an in-depth explanation for each modifier; instead, I want to describe at a higher level what's happening in the code snippet above.\n\nWe are trying to build a card-like UI, where we define `Text` views displaying the values for our `title` and `message` properties. We then define a `RoundedRectangle` shape as a background to our `VStack` view.\n\nWhat's interesting is the content in our `HStack`. We are returning the `View` objects defined by the user and stored on the `primaryAction` and `secondaryAction` properties. We are assuming that `primaryAction` stores a button with a _destructive_ role (right-hand side), while `secondaryAction` stores one with a _cancel_ role (left-hand side).\n\n### Pros and Cons of the Above Implementation\n\nNow that we have gone over the first implementation layer for our `ModalCard` component, if you are a detail-oriented person, you might have noticed some cons to this implementation, especially after telling you that I meant for this card view to display buttons for actions to perform.\n\nThen, in an ideal world, an upright user that would read the documentation attached to my SwiftUI component would know what to do and use the `ModalCard` view API much like the following:\n\n```swift\nModalCard(\n    title: \"Delete Account\",\n    message: \"This action cannot be undone.\",\n    primaryAction: {\n        Button(\"Delete\") {\n            print(\"Delete\")\n        }\n    },\n    secondaryAction: {\n        Button(\"Cancel\") {\n            print(\"Cancel\")\n        }\n    }\n)\n```\n\nThis is how the `ModalCard` should be used, and `primaryAction` and `secondaryAction` should be assigned functions returning `Button` objects.\n\nHowever, there are some cons to this, and if you are reasoning through things like a framework designer, you might notice that we are actually allowing the user to pass over to the `ModalCard` constructor whichever `View` objects they want. That means that someone could pass in a `Text`, an `Image`, or even a `ProgressView`, and our component wouldn't complain!\n\nFor instance, this is weird, yet legal:\n\n```swift\nModalCard(\n    title: \"Oops\",\n    message: \"This modal has weird content.\",\n    primaryAction: {\n        Image(systemName: \"xmark.circle\")\n    },\n    secondaryAction: {\n        Text(\"Not really a button\")\n    }\n)\n```\n\nIf our intention was just to allow buttons, then the code above is nonsensical, isn't it?\n\nThen why use it?\n\nLet me walk you through this, and that's where things get hotter and more interesting.\n\nThere's actually a clear trade-off here, which is a very common one for framework engineers:\n\n1. Using `View` (generic):\n   - **Pros**: Very flexible and allows the component to be extremely adaptive.\n   - **Cons**: No constraints defined, which may lead to misuse and nonsensical behaviors.\n\n2. Using a restricted type (e.g., `Button`):\n   - **Pros**: Forces intended usage.\n   - **Cons**: Reduces flexibility (e.g., custom-styled buttons, or conditional logic).\n\nSo, I'll tell you right off the bat that Apple leans towards the first approach, which is using generics, but with a more clever implementation that allows for encapsulation, flexibility, and scalability.\n\nHowever, before showing you how I would add a second layer to our current version of `ModalCard` to come up with a more Apple-like version, I want to first point out to you why using a restricted type (2. approach) is very limiting, which I suggest you not go for it.\n\n### Implementing `ModalCard` with a Restricted-Type Approach (Not Recommended)\n\nSo, I'm just showing you this restricted-type approach to stress over the fact that you shouldn't use it.\n\nTake the following implementation of `ModalCard` using this approach, which enforces the user of the component to pass over a `Button` under the `primaryAction` and `secondaryAction` parameters:\n\n```swift\npublic struct ModalCard: View {\n\n  // MARK: - Properties\n\n  let title: String\n  let message: String\n  let primaryAction: Button\u003cText\u003e\n  let secondaryAction: Button\u003cText\u003e\n\n  // MARK: - Init\n  \n  public init(\n    title: String,\n    message: String,\n    primaryAction: Button\u003cText\u003e,\n    secondaryAction: Button\u003cText\u003e\n  ) {\n    self.title = title\n    self.message = message\n    self.primaryAction = primaryAction\n    self.secondaryAction = secondaryAction\n  }\n\n  // MARK: - Body\n\n  public var body: some View {\n      VStack(spacing: 15) {\n          Text(title)\n            .font(.headline)\n            .foregroundStyle(.primary)\n\n          Text(message)\n            .font(.subheadline)\n            .multilineTextAlignment(.center)\n            .foregroundStyle(.secondary)\n          \n          HStack(spacing: 15) {\n              secondaryAction\n              primaryAction\n          }\n          .padding()\n    }\n    .padding()\n    .background(\n      RoundedRectangle(cornerRadius: 20, style: .continuous)\n        .fill(Color.white)\n        .shadow(color: Color.black.opacity(0.1), radius: 5, x: 0, y: 2)\n    )\n    .padding()\n  }\n}\n```\n\nThe code above is almost similar to the generic implementation, but we are now forcing a type of `Button\u003cText\u003e` to be passed by the user, which is now meeting the expectations of our `ModalCard` components, and forces consistent UI logic across the view. However, we can't pass styled buttons, or any other type of buttons. For instance, a `Button` with an `Image` and `Text` isn't allowed. Therefore, it doesn't support cases where our buttons might be more complex than `Button\u003cText\u003e`.\n\nFor instance, this button wouldn't be allowed for the restricted-type implementation:\n\n```swift\nButton {\n    print(\"OK\")\n} label: {\n    Label(\"OK\", systemImage: \"checkmark\")\n}\n```\n\nThat's because that's not a `Button\u003cText\u003e` type; rather, it's a `Button\u003cLabel\u003cText, Image\u003e\u003e` type.\n\nFurthermore, this other implementation would also be restrictive, which is pretty similar to the one I showed you right above, with the only difference being that we are to decide which type of `Button` to store, and limit the user to defining just the action of those buttons.\n\n```swift\npublic struct ModalCard: View {\n\n  // MARK: - Properties\n\n  let title: String\n  let message: String\n  let primaryAction: Button\u003cText\u003e\n  let secondaryAction: Button\u003cText\u003e\n\n  // MARK: - Init\n  \n  public init(\n    title: String,\n    message: String,\n    primaryAction: @escaping () -\u003e Void,\n    secondaryAction: @escaping () -\u003e Void\n  ) {\n    self.title = title\n    self.message = message\n    self.primaryAction = Button(\"Delete\", action: primaryAction)\n    self.secondaryAction = Button(\"Cancel\", action: secondaryAction)\n  }\n\n  // MARK: - Body\n\n  public var body: some View {\n      VStack(spacing: 15) {\n          Text(title)\n            .font(.headline)\n            .foregroundStyle(.primary)\n\n          Text(message)\n            .font(.subheadline)\n            .multilineTextAlignment(.center)\n            .foregroundStyle(.secondary)\n          \n          HStack(spacing: 15) {\n              secondaryAction\n              primaryAction\n          }\n          .padding()\n    }\n    .padding()\n    .background(\n      RoundedRectangle(cornerRadius: 20, style: .continuous)\n        .fill(Color.white)\n        .shadow(color: Color.black.opacity(0.1), radius: 5, x: 0, y: 2)\n    )\n    .padding()\n  }\n}\n```\n\n### Second Layer to our `ModalCard`: Providing Encapsulation and Predictiveness (Recommended)\n\nYou now see why I don't suggest using the restricted-type approach; instead, I got you covered with a better generic approach: I will turn my first implementation layer to `ModalCard` into a much more predictive solution, which restricts the choice to the kinds of options that we'd like the user to choose out of; in other words, we control which options are given to the user. This is also scalable, because we are going to update our `ModalCard` struct in such a way that, in later versions of our API, we can also add further options. You will also see how we can abstract away the need for the user to pass over the entire `View` object, and encapsulate the nitty-gritty to provide the user with a better and cleaner interface to deal with.\n\nThe following is an approach similar to Apple; that is, Apple provides full `View` flexibility, while documenting the expected usage, and coming up with convenience overloads for pre-defined options.\n\nBefore starting, I took inspiration from one of the native SwiftUI components; namely, the `Alert` view. I wanted to build something similar, so I started reverse-engineering it. The initializer of the `Alert` struct that I took inspiration from is the following:\n\n```swift\nAlert(\n  title: Text(\"My inspiration component\"),\n  primaryButton: .destructive(Text(\"Delete\"), action: {}),\n  secondaryButton: .cancel()\n)\n```\n\nActually, many of SwiftUI's native components are built using a similar pattern.\n\nGo into Xcode, or your Swift playground, and try it. You'll notice that either under `primaryButton`, or `secondaryButton`, you are provided options.\n\nWhenever you write `.\u003coption\u003e`, just know that you are most likely tapping into either a `static` method, or a `static` computed property. `static` is the key, because it allows for abstraction, and encapsulation of the main code that actually creates those buttons, and the user is provided with a pre-defined interface where they just need to type in the related contents, under specific parameters.\n\nAlso, if you peek into the type of the `primaryButton` parameter, you'll read `Alert.Button`, which means that Apple has build a custom `Button` struct inside of the `Alert` struct, which is totally different from the `SwiftUI.Button` type, our good ol' button.\n\nThat means that when you type `.destructive(...)`, you are tapping into a specific `static` method on the `Button` struct within the `Alert` struct (`Alert.Button`), and that's the one that builds that specific kind of button with a _destructive_ role.\n\nTherefore, our `Alert.Button` struct is such that it controls which `Button` type to be returned depending on which `static` method the user taps into.\n\nAs a side node, a `static` method or property, is such that it belongs to the `struct` or `class` object itself, and not to any of their instances. However, since a `static` property belongs to the type object itself, any instances of that type (e.g., `Alert`) can tap into that `static` property, which also means that's being shared among all instances of that type.\n\nWith that out of the way, let's look at how we would go about implementing a **predictive**, **safe**, and **encapsulating** approach similar to Apple-style APIs. However, I'll also show you why, for our implementation, we won't need to make use of generics; instead, we will expose a **high-level**, **semantic** **API** that reads nicer — much like the `Alert.Button.destructive(...)` semantic — using a common design pattern used by Apple: the **Static Factory Method** Design Pattern, strategized and supported with our dear `enum` friend.\n\nFirst off, let me show you why you wouldn't want to use **generics** when implementing a supporting struct that applies the Factory Method design pattern. \n\nHowever, before we do this, let's actually create this supporting struct to our `ModalCard` view, much similar to how Apple builds it within the native `Alert` struct — `Alert.Button`.\n\n```swift\npublic struct ModalCard\u003cPrimary: View, Secondary: View\u003e: View {\n\n  // MARK: - ModalCard.Button (Factory struct)\n\n  public struct Button {\n        \n        enum ButtonType {\n            case destructive(label: Text, action: () -\u003e Void)\n            case cancel(action: () -\u003e Void)\n        }\n        \n        public static func destructive(_ label: Text, _ action: @escaping () -\u003e Void) -\u003e ModalCard.Button {\n            return Button(type: .destructive(label: label, action: action))\n        }\n        \n        public static func cancel(_ action: @escaping () -\u003e Void) -\u003e ModalCard.Button {\n            return Button(type: .cancel(action: action))\n        }\n        \n        @ViewBuilder\n        fileprivate func render() -\u003e some View {\n            switch self.type {\n            case .destructive(let label, let action):\n                SwiftUI.Button(action: action, label: { label })\n            case .cancel(let action):\n                SwiftUI.Button(action: action, label: { Text(\"Cancel\") })\n            }\n        }\n        \n        private var type: ButtonType\n        \n        private init(type: ButtonType) {\n            self.type = type\n        }\n    }\n\n  // MARK: - Properties\n\n  let title: String\n  let message: String\n  let primaryButton: Primary\n  let secondaryButton: Secondary\n\n  // MARK: - Init\n  \n  public init(\n    title: String,\n    message: String,\n    primaryButton: ModalCard.Button,\n    secondaryButton: ModalCard.Button\n  ) {\n    self.title = title\n    self.message = message\n    self.primaryButton = primaryButton.render() as! Primary\n    self.secondaryButton = secondaryButton.render() as! Secondary\n  }\n\n  // MARK: - Body\n\n  public var body: some View {\n      VStack(spacing: 15) {\n          Text(title)\n            .font(.headline)\n            .foregroundStyle(.primary)\n\n          Text(message)\n            .font(.subheadline)\n            .multilineTextAlignment(.center)\n            .foregroundStyle(.secondary)\n          \n          HStack(spacing: 15) {\n              secondaryButton\n              primaryButton\n          }\n          .padding()\n    }\n    .padding()\n    .background(\n      RoundedRectangle(cornerRadius: 20, style: .continuous)\n        .fill(Color.white)\n        .shadow(color: Color.black.opacity(0.1), radius: 5, x: 0, y: 2)\n    )\n    .padding()\n  }\n}\n```\nLet me walk you through it, step-by-step:\n\n```swift\npublic struct ModalCard\u003cPrimary: View, Secondary: View\u003e: View {\n  public struct Button {\n```\n\n1. Since the `Button` struct is meant to support the `ModalCard` struct, and relates to it, I define it within the `ModalCard` struct. Yes, that's a common pattern for when you have a type that associates with and belongs to another one. In this example case, `Button` is going to be part of the `ModalCard` struct and is going to be a completely different `struct` from our native `SwiftUI.Button`; in that, `ModalCard.Button` is our **Factory struct**, which provides a **semantic API surface** and **encapsulates** the internal implementation detail from the users of our `ModalCard` API.\n\n```swift\n// MARK: - Properties\n\n  let title: String\n  let message: String\n  let primaryButton: Primary\n  let secondaryButton: Secondary\n```\n\n2. Notice that I also changed the name of our properties from `primaryAction` and `secondaryAction` to `primaryButton` and `secondaryButton`, since our implementation is fully predictive and we know what to expect from the user; that's because _we_ are now to decide and implement the options as well as what to return internally — `Button` objects, in our case — via our new interface.\n\n```swift\n// Within the `ModalCard.Button` struct\n\nenum ButtonType {\n  case destructive(label: Text, action: () -\u003e Void)\n  case cancel(action: () -\u003e Void)\n}\n```\n\n3. Why define a helper `ButtonType` `enum`? Well, an `enum` is a supporting strategy to our Factory Method Design Pattern. It serves as a bridging between the `ModalCard` and `ModalCard.Button` to communicate which `SwiftUI.Button` to render within the `body` property at due time. This pattern is also used by SwiftUI to allow communication between factory structs (e.g., `Font`), and the appropriate modifier (e.g., `.font()` modifier) of type `ViewModifier`, as the SwiftUI likely uses an `enum` or `descriptor` to talk to the `ViewModifier` for it to know which `TextStyle` to apply, for example, which will eventually be written to the environment of the `View` object the `.font()` modifier gets called on. We are using the same pattern here, and we will eventually have the `ModalCard.Button` factory struct return a `ModalCard.Button` instance holding the configuration info as to what type of button to render. Also, notice that I have defined two cases for the `ButtonType` enum: `destructive`, and `cancel`. Both of them have associated values because we need to store information being passed by the users of the API; namely, either the `action` to perform, as well as the `label` for our buttons.\n\n```swift\n// Within the `ModalCard.Button` struct\n\n// Inner Workings of `ModalCard.Button`, which are abstracted away from the user\nprivate type: ButtonType\n\nprivate init(type: ButtonType) {\n  self.type = type\n}\n```\n\n4. Then, I go about designing the internals of the `ModalCard.Button` struct. They are encapsulated by making use of the `private` access modifier. I go about defining how each `Button` instance is created, as well as its instance members — `type` property. The `ModalCard.Button` instance will be assigned a value to its `type` property upon its instantiation, depending on which `static` method the user calls. This is how we know which `SwiftUI.Button` to render.\n\n```swift\n// Within the `ModalCard.Button` struct\n\n// User option for `destructive` role (semantic API user interface)\npublic static func destructive(_ label: Text, _ action: @escaping () -\u003e Void) -\u003e ModalCard.Button {\n  return Button(type: .destructive(label: label, action: action))\n}\n\n// User option for `cancel` role (semantic API user interface)\npublic static func cancel(_ action: @escaping () -\u003e Void) -\u003e ModalCard.Button {\n  return Button(type: .cancel(action: action))\n}\n```\n\n5. Finally, our precious `static` factory methods. Obviously, we define them with a `public` access modifier, as they need to be used by our users from outside the `ModalCard` module. Each factory method produces an instance of `ModalCard.Button` and passes over the relative value to its `type` property of type `ButtonType`. Also, we get to define the proper associated values for our `enum`, depending on the case. Those associated values are important because they allow us to gather information from the user, which will then be passed to the constructor of the `SwiftUI.Button`. Let me show you how I built the instance method to render the appropriate `SwiftUI.Button` object.\n\n```swift\n// Within the `ModalCard.Button` struct\n\n@ViewBuilder\nfileprivate func render() -\u003e some View {\n  switch self.type {\n    case destructive(let label, let action):\n      SwiftUI.Button(action: action, label: { label })\n    case cancel(let action):\n      SwiftUI.Button(action: action, label: { Text(\"Cancel\") })\n  }\n}\n```\n6. In order to render our `SwiftUI.Button` view, I created an instance method called `render`. Notice how I gave it a `fileprivate` access modifier. Can you guess why? Well, this method needs to be called from within the `body` property of the `ModalCard` struct, so we made it private to the _file_, and not to the `ModalCard.Button` _struct_ itself. The `some View` opaque return type is **key** to scalability of our `ModalCard` component, on top of being the main reason why I avoided using generics, as I have previously mentioned. I'll explain to you in a second.\n\n### Why Avoid Using Generics\n\nAt this point, If you tried building your `ModalCard` object using the version that uses **generics**, which I lastly shared with you,\n\n```swift\nimport ModalCard\n\nModalCard(\n    title: \"Delete Account\",\n    message: \"This action cannot be undone.\",\n    primaryButton: .destructive(\n        Text(\"Delete\"),\n        { print(\"Delete\") }\n    ),\n    secondaryButton: .cancel(\n        { print(\"Cancel\") }\n    )\n)\n```\nit won't work, and that's due to how we structured our Factory struct (`ModalCard.Button`); specifically, our `render()` method.\n\nWe are currently using our `render()` method inside the initializer of our `ModalCard` to assign _whichever_ object of type `View` is being returned from it to both `primaryButton`, and `secondaryButton`.\nThen, I force-cast that returned type to the `Primary` and `Secondary` generic types, because those are the types we declared our properties to be.\n\n```swift\n// Within the `ModalCard` struct\npublic init(\n  title: String,\n  message: String,\n  primaryButton: ModalCard.Button,\n  secondaryButton: ModalCard.Button\n) {\n  self.title = title\n  self.message = message\n\n  // Force casting an opaque type to a generic type\n  self.primaryButton = primaryButton.render() as! Primary\n  self.secondaryButton = secondaryButton.render() as! Secondary\n}\n```\n\nYou might ask to yourself, \"Well, we know that what's returned by the `render()` method is some object of type `View`, and we also know that either the `Primary` or `Secondary` generic type are some types conforming to the `View` protocol, so what's the problem with force-casting?\"\n\nWell, I know that it might sound plausible, but it actually isn't, and I'll explain to you why.\n\n#### The Problem with `as! Primary`\n\nWe know that the `some` keyword defines an **opaque return type**. The opaque return type hides the actual underlying type — actual type being returned — and it lets Swift infer the type at runtime. In our specific case, we are hiding the actual returned type behind the `View` protocol. \n\nTherefore, even though the Swift compiler knows that the underlying type conforms to `View`, Swift can't know whether this real `View` underlying type being returned by `render()` actually matches with the `Primary` type. Yes, `Primary` also conforms to `View`, but that doesn't mean that the view being returned by `render` is going to be exactly of the same type specified by `Primary`. \n\nFor instance, `Primary` might hold a type of `Text` — still conforming to `View` — while the actual underlying `View` being returned by the `render()` method is of type `Button`, which still conforms to `View`, but at the end of the day they are _not_ matching types. That's why Swift rightfully complains about this and prevents it from happening at runtime. It's like telling Swift to trust us that whatever `some View` returns is definitely the same as a completely unrelated generic type `Primary`. Well, if you think about it, Swift cannot know it since it's implicit within the definition of generics: either `Primary` or `Secondary` can hold _any_ type conforming to `View`.\n\n#### Solution to Force-Cast\n\nIf you think about it, we have already laid the foundation for a flexible, scalable, and safe code using our `ModalCard.Button` supporting struct and applying the _Factory Method_ design pattern.\n\nIn fact, the main reason why we came up with such a solution was to have our factory struct `ModalCard.Button` _produce_ buttons, and `ModalCard` _accept_ those views. We don't need generics at all — in our case, we don't need `Primary: View` and `Secondary: View` — because the flexibility attribute that generics could have offered us is being resolved by the following steps:\n\n- Having `ModalCard` store the `ModalCard.Button` object directly under `primaryButton` and `secondaryButton`.\n  \n- Delegating the rendering to `render() -\u003e some View`, which offers us the flexibility we need via the _return opaque type_ — the method returns any object conforming to `View` — while providing safety and encapsulation by deciding which options (static methods) to expose to the end user — our developers — when designing the `ModalCard.Button` factory struct. In such a situation, our `ModalCard.Button` object holds the necessary information regarding which `Button` view to render; therefore, we are no longer force-casting the underlying `View` type, but directly embedding it within the `body` property of our `ModalCard` view.\n\nFinally, the pattern we are going to use mimics Apple's `Alert.Button` style almost exactly.\n\n### Final Implementation of `ModalCard`\n\nWe understood that generics aren't always needed, and despite them being useful, we should use them whenever the end-user of our API _injects_ their logic, but that also comes with its risks.\n\nHowever, in scenarios where we are to own the logic, for example, with a supporting factory struct, we don't need generics.\n\nTherefore, let's finally have a look at the final implementation of our `ModalCard` component, where we make the most out of our factory design pattern and allow smooth communication between the outer and inner structs:\n\n```swift\npublic struct ModalCard: View {\n\n  // MARK: - `ModalCard.Button` factory struct\n\n  public struct Button {\n\n    // Exposed factory methods\n\n    public static func destructive(_ label: Text, _ action: @escaping () -\u003e Void) -\u003e ModalCard.Button {\n      return Button(type: .destructive(label: label, action: action))\n    }\n\n    public static func cancel(_ action: @escaping () -\u003e Void) -\u003e ModalCard.Button {\n      return Button(type: .cancel(action: action))\n    }\n\n    // Rendering method exposed to `ModalCard`\n    @ViewBuilder\n    fileprivate func render() -\u003e some View {\n      switch self.type {\n      case .destructive(let label, let action):\n        SwiftUI.Button(action: action, label: { label })\n      case .cancel(let action):\n        SwiftUI.Button(action: action, label: { Text(\"Cancel\") })\n      }\n    }\n\n    // Encapsulated behaviors\n    \n    private enum ButtonType {\n      case destructive(label: Text, action: () -\u003e Void)\n      case cancel(action: () -\u003e Void)\n    }\n\n    private var type: ButtonType\n\n    private init(type: ButtonType) {\n      self.type = type\n    }\n  }\n\n  // MARK: - Properties\n\n  let title: String\n  let message: String\n  let primaryButton: ModalCard.Button\n  let secondaryButton: ModalCard.Button\n\n  // MARK: - Init\n\n  public init(\n    title: String,\n    message: String,\n    primaryButton: ModalCard.Button,\n    secondaryButton: ModalCard.Button\n  ) {\n    self.title = title\n    self.message = message\n    self.primaryButton = primaryButton\n    self.secondaryButton = secondaryButton\n  }\n\n  // MARK: - Body\n\n  public var body: some View {\n      VStack(spacing: 15) {\n          Text(title)\n            .font(.headline)\n            .foregroundStyle(.primary)\n\n          Text(message)\n            .font(.subheadline)\n            .multilineTextAlignment(.center)\n            .foregroundStyle(.secondary)\n          \n          HStack(spacing: 15) {\n              secondaryButton.render()\n              primaryButton.render()\n          }\n          .padding()\n    }\n    .padding()\n    .background(\n      RoundedRectangle(cornerRadius: 20, style: .continuous)\n        .fill(Color.white)\n        .shadow(color: Color.black.opacity(0.1), radius: 5, x: 0, y: 2)\n    )\n    .padding()\n  }\n}\n```\n\nLet me walk you through the main changes with our final and working implementation, which is the one I provided you with within the `ModalCard.swift` file on this repository:\n\n```swift\npublic struct ModalCard: View {\n```\n\n1. We are no longer using generics — we no longer define the `Primary: View` and `Secondary: View` type parameters.\n\n```swift\n// Within the `ModalCard` struct\n\n// MARK: - Init\n\n  public init(\n    title: String,\n    message: String,\n    primaryButton: ModalCard.Button,\n    secondaryButton: ModalCard.Button\n  ) {\n    self.title = title\n    self.message = message\n    self.primaryButton = primaryButton\n    self.secondaryButton = secondaryButton\n  }\n```\n\n2. We have `ModalCard` accept the `ModalCard.Button` instances produced by the factory struct (`ModalCard.Button`), and store them under `primaryButton` and `secondaryButton`. These instances are crucial as they hold information as to which `View` to render; specifically, which `SwiftUI.Button` type to render, based on the option that the end-user passed to the exposed interface (e.g., `ButtonType.destructive`).\n\n```swift\n// Within the `body` property of `ModalCard`\n\nHStack(spacing: 15) {\n  secondaryButton.render()\n  primaryButton.render()\n```\n\n3. As we mentioned in the previous paragraph, we delegate the rendering to `render() -\u003e some View`, which will just embed whichever type of `View` returned into the `HStack`. This offers us the flexibility we were looking for. However, this flexibility is controlled for predictiveness and safety by setting up our constraints within the factory struct.\n\n#### Usage Example\n\nSo, what you are left with is just trying the `ModalCard` component! You'll find that its setup is very similar to how Apple constructed its native `Alert` component.\n\nThe following is an example showing you how you would want to instantiate and use the `ModalCard` struct:\n\n```swift\nModalCard(\n  title: \"Delete Account\",\n  message: \"This action cannot be undone.\",\n  primaryButton: .destructive(\n      Text(\"Delete\"),\n      { print(\"Delete\") }\n  ),\n  secondaryButton: .cancel(\n      { print(\"Cancel\") }\n  )\n)\n```\n\n### Briefly on Design Choice\n\nThe `ModalCard` component is an interesting example of combining multiple design patterns to achieve clarity, reusability, and expressiveness.\n\n#### Factory Design Pattern\n\nThe primary design pattern is the _Factory_ design pattern. This pattern is a creational design pattern, which exposes a method to the user of our class or struct to create well-defined instances of a certain type. In our case, we exposed `static` methods (e.g., `.destructive`, or `.cancel`) to produce `ModalCard.Button` instances; for this reason, we can specifically refer to it as _Static Factory Method_.\n\n```swift\npublic static func destructive(_ label: Text, _ action: @escaping () -\u003e Void) -\u003e ModalCard.Button {\n```\n\nMethods such as the one in the code snippet above encapsulate the internal implementation details (e.g., enums, properties, and constructor) and expose the necessary features offering a semantic API surface to the end-user.\n\nAs you may have noticed, many of Apple's APIs, such as `Alert` (e.g., `Alert.Button.destructive`) are also implemented using the Static Factory Method design pattern, because it _encapsulates_ construction logic, and provides _semantic access_.\n\n#### Strategy-like Design Pattern\n\nAs a premise, I'm claiming that the codebase of our `ModalCard` uses a design pattern _similar_ to the **Strategy** Design Pattern, because it applies the same concepts. However, it doesn't resemble the exact implementation, and I'll explain to you why in a second.\n\nFirst off, a **Strategy** Design Pattern lets you define a series of algorithms (actions) to embed in separate classes or structs, each of these classes or structs is meant to implement a certain interface or protocol (in Swift) called the **Strategy** protocol. This protocol will define a requirement, which is the implementation of a concrete strategy or action for each class or struct.\n\nFor instance, in our `ModalCard` example, the Strategy protocol would be the `ButtonType`, and the structs adopting that protocol would be `Destructive` and `Cancel`. Either `Destructive` or `Cancel` struct is a **Concrete Strategy**, because we provide a _concrete_ implementation for the strategy or action — rendering a certain `SwiftUI.Button` object.\n\nThese concrete strategy objects are then going to be interchangeable on the `ModalCard.Button` struct. This struct is called the context struct, which stores the actual strategy object — any object adopting the `ButtonType` protocol — and also defines an interface to have the strategy object manipulate its data and perform specific strategy actions with it – rendering a certain `SwiftUI.Button` object. In other words, the `ModalCard.Button` context struct would use a property of type `ButtonType` to invoke a specific algorithm/action defined by the concrete strategy (e.g., object of type `Destructive` adopting the `ButtonType` strategy) we store on that property. This a truly flexible tool, because we can change the behavior of our `ModalCard` at runtime, just by using an interface that replaces a new concrete strategy object with a new one, which performs a different action. For example, in our `ModalCard` example case, that property would be our `type` property, right?\n\nI'll show you an example of `ModalCard.Button` using the pure Strategy Design Pattern just for demonstration purposes and explain to you how it relates to the final implementation of `ModalCard` and why I decided not to go fully into implementing it:\n\n```swift\npublic struct ModalCard: View {\n\n  // MARK: - `ModalCard.Button` factory/context struct\n\n  public struct Button {\n      \n      // Define static factory methods: semantic API interface\n      \n      public static func destructive(_ label: Text, _ action: @escaping () -\u003e Void) -\u003e ModalCard.Button {\n          Button(type: AnyButtonType(Destructive(label: label, action: action)))\n      }\n      \n      public static func cancel(_ action: @escaping () -\u003e Void) -\u003e ModalCard.Button {\n          Button(type: AnyButtonType(Cancel(action: action)))\n      }\n      \n      // Define the `ButtonType` strategy protocol\n      private protocol ButtonType {\n          associatedtype ViewType: View\n          \n          @ViewBuilder\n          func render() -\u003e ViewType\n      }\n      \n      // Apply type erasure using `AnyButtonType` to wrap any conformer\n      // to `ButtonType`. A type-erased `ButtonType` strategy\n      private struct AnyButtonType: ButtonType {\n          \n          let _render: () -\u003e AnyView\n          \n          fileprivate init\u003cT: ButtonType\u003e(_ wrapped: T) {\n              self._render = { AnyView(wrapped.render()) }\n          }\n          \n          @ViewBuilder\n          func render() -\u003e some View {\n              self._render()\n          }\n      }\n      \n      // Define `destructive` concrete strategy\n      private struct Destructive: ButtonType {\n          \n          let label: Text\n          let action: () -\u003e Void\n          \n          @ViewBuilder\n          func render() -\u003e some View {\n              SwiftUI.Button(action: action, label: { label })\n          }\n      }\n      \n      // Define `cancel` concrete strategy\n      private struct Cancel: ButtonType {\n          \n          let action: () -\u003e Void\n          \n          @ViewBuilder\n          func render() -\u003e some View {\n              SwiftUI.Button(action: action, label: { Text(\"Cancel\") })\n          }\n      }\n      \n      // Expose interface to `ModalCard` to render buttons\n      @ViewBuilder\n      fileprivate func render() -\u003e some View {\n          type.render()\n      }\n      \n      // Define the property storing the `ButtonType` strategy object\n      private var type: AnyButtonType\n      \n      private init(type: AnyButtonType) {\n          self.type = type\n      }\n  }\n\n  // MARK: - Properties\n\n  let title: String\n  let message: String\n  let primaryButton: ModalCard.Button\n  let secondaryButton: ModalCard.Button\n\n  // MARK: - Init\n\n  public init(\n    title: String,\n    message: String,\n    primaryButton: ModalCard.Button,\n    secondaryButton: ModalCard.Button\n  ) {\n    self.title = title\n    self.message = message\n    self.primaryButton = primaryButton\n    self.secondaryButton = secondaryButton\n  }\n\n  // MARK: - Body\n\n  public var body: some View {\n      VStack(spacing: 15) {\n          Text(title)\n            .font(.headline)\n            .foregroundStyle(.primary)\n\n          Text(message)\n            .font(.subheadline)\n            .multilineTextAlignment(.center)\n            .foregroundStyle(.secondary)\n          \n          HStack(spacing: 15) {\n              secondaryButton.render()\n              primaryButton.render()\n          }\n          .padding()\n    }\n    .padding()\n    .background(\n      RoundedRectangle(cornerRadius: 20, style: .continuous)\n        .fill(Color.white)\n        .shadow(color: Color.black.opacity(0.1), radius: 5, x: 0, y: 2)\n    )\n    .padding()\n  }\n}\n```\nLet me walk you through the above implementation for `ModalCard.Button`, which uses the _Strategy Design Pattern_ at its fullest, alongside the _Factory Design Pattern_, which we have already gone through.\n\n```swift\n// Define the `ButtonType` strategy protocol\nprivate protocol ButtonType {\n    associatedtype ViewType: View\n    \n    @ViewBuilder\n    func render() -\u003e ViewType\n}\n```\n\n1. The most important aspect to implementing the Strategy pattern is defining a protocol to be adopted by all versions of a certain algorithm or action — the `render()` action, in our specific case. The context struct `ModalCard.Button` uses our `ButtonType` strategy protocol to be able to interchange amongst objects conforming to `ButtonType` at runtime — concrete strategies. Because of that, the `ModalCard.Button` context has the ability to call the rendering action defined by a specific concrete strategy (e.g., `Destructive`).\n\n```swift\n// Define `destructive` concrete strategy\nprivate struct Destructive: ButtonType {\n    \n    let label: Text\n    let action: () -\u003e Void\n    \n    @ViewBuilder\n    func render() -\u003e some View {\n        SwiftUI.Button(action: action, label: { label })\n    }\n}\n\n// Define `cancel` concrete strategy\nprivate struct Cancel: ButtonType {\n    \n    let action: () -\u003e Void\n    \n    @ViewBuilder\n    func render() -\u003e some View {\n        SwiftUI.Button(action: action, label: { Text(\"Cancel\") })\n    }\n}\n```\n\n2. The different versions of an algorithm/action are represented by the concrete strategy classes or structs adopting the Strategy interface/protocol. In our case, we defined two concrete strategies — `Destructive` and `Cancel`, both adopting the `ButtonType` strategy protocol.\n\n```swift\n// Define the property storing the `ButtonType` strategy object\nprivate var type: AnyButtonType\n\nprivate init(type: AnyButtonType) {\n    self.type = type\n}\n```\n\n3. We then define a reference to a strategy object within our `ModalCard.Button` context struct; specifically, we store the strategy object on the `type` property.\n\n```swift\n// Define static factory methods: semantic API interface for encapsulating internal implementation details\n      \npublic static func destructive(_ label: Text, _ action: @escaping () -\u003e Void) -\u003e ModalCard.Button {\n    Button(type: AnyButtonType(Destructive(label: label, action: action)))\n}\n\npublic static func cancel(_ action: @escaping () -\u003e Void) -\u003e ModalCard.Button {\n    Button(type: AnyButtonType(Cancel(action: action)))\n}\n\n// Expose interface to `ModalCard` to render buttons\n@ViewBuilder\nfileprivate func render() -\u003e some View {\n    type.render()\n}\n```\n\n4. Then, the context defines an interface to either manipulate the strategy object or have it access the data on the context itself. In our case, our interface involves both the `render()` method — which uses a `fileprivate` access modifier to expose it to `ModalCard` to render buttons — and the static factory methods (Factory Design Pattern) used to produce instances of `ModalCard.Button` with a specific concrete strategy on the `type` property for a determined button-rendering behavior.\n\n#### The Problem with Using `ButtonType` protocol as a Type to the `type` Property\n\nNow, at this point, you might have had some doubts about step number 3, where we have the following code:\n\n```swift\n// Define the property storing the `ButtonType` strategy object\nprivate var type: AnyButtonType\n\nprivate init(type: AnyButtonType) {\n    self.type = type\n}\n```\n\nAnd asked yourself why we didn't end up with the following code, instead:\n\n```swift\n// Define the property storing the `ButtonType` strategy object\nprivate var type: ButtonType\n\nprivate init(type: ButtonType) {\n    self.type = type\n}\n```\n\nThe code above would have been more plausible as far as the Strategy Design Pattern is concerned. After all, the context struct is expected to store any concrete strategy conforming to the `ButtonType` protocol to run its specific rendering behavior. So, where is the gotcha?\n\nWell, Swift would have allowed us to write the code right above if we hadn't had an associated type within the definition of our `ButtonType` protocol.\n\nHowever, the definition of an associated type within our protocol is crucial, in our case, because it allows us to define the different concrete implementations for the `render()` method on each concrete strategy struct (`Destructive` and `Cancel`) to return an opaque return type — `some View`, in our case — which is key to rendering any underlying `View` being inferred by Swift upon returning them.\n\nSo, if we had used the `ButtonType` directly as a type to the `type` property — I know, too many \"type\" words — that wouldn't have worked, and we would have had a compiler error. That's because we _cannot_ use a protocol with an associated type as a _concrete_ type for a stored property. This isn't allowed in Swift.\n\nWe know that to accomplish the Strategy pattern, the `type` property on the context struct is meant to wrap any object conforming to the `ButtonType` strategy protocol. Therefore, a solution to this problem is creating a _concrete_ type that wraps any object of type `ButtonType` using generics, and the name for this solution is **Type Erasure**.\n\nIn our case, **Type Erasure** will encapsulate different `ButtonType` conformers inside a single `AnyButtonType` wrapper, which will be defined as a struct. As a convention, we tend to name the type-eraser/type-wrapper using the \"Any\" prefix attached to the name of the type to be erased/wrapped — `ButtonType`, in our case. Therefore, `AnyButtonType` is also said to be a \"type-erased\" `ButtonType`.\n\nThe following is the code for the `AnyButtonType` wrapper struct:\n\n```swift\nprivate struct AnyButtonType: ButtonType {\n\n  let _render: () -\u003e AnyView\n  \n\n  fileprivate init\u003cT: ButtonType\u003e(_ wrapped: T) {\n    self._render = { AnyView(wrapped.render()) }\n  }\n\n  @ViewBuilder\n  func render() -\u003e some View {\n    return self._render()\n  }\n}\n```\n\nYou see that we are now using a concrete type (`AnyButtonType`) that wraps any type conforming to the `ButtonType` protocol. If you think about it, that's a type-erasure operation, in the sense that we don't care about the type that's being wrapped, as long as it conforms to the `ButtonType` protocol. This operation finally erases the type, because the actual type that's being returned is represented by the type-eraser itself, which happens to be `AnyButtonType`, in our specific case. Eventually, the original type of the wrapped object is lost or erased.\n\n```swift\n// Within the `AnyButtonType` wrapper\n\nfileprivate init\u003cT: ButtonType\u003e(_ wrapped: T) {\n```\n\nAlso, notice how generics is a _fundamental_ feature for type erasure: it wouldn't be possible to wrap any concrete strategy type conforming to the `ButtonType` strategy protocol without generics. In our case, we defined a generic type parameter with a `ButtonType` conformance (`T: ButtonType`) for the initializer on `AnyButtonType`. The `AnyButtonType` struct is instantiated with a concrete strategy conforming to `ButtonType`, passed under the `wrapped` parameter — we use the type parameter `T` as its type.\n\n```swift\n// Within the `AnyButtonType` wrapper\n\nlet _render: () -\u003e AnyView\n```\n\nThe wrapper struct defines a `_render` property, which has a function type `() -\u003e AnyView`. Notice how we are, again, using type erasure; in fact, the function returns a type-erased `View`. That's because `_render` is a stored property, and cannot be assigned an opaque return type. That means we couldn't type-annotate `_render` as `() -\u003e some View`, because Swift would be expecting an initializer expression from which to infer an underlying type. For how we define our initializer within `AnyButtonType`, we are not able to have Swift infer the concrete type that's being hidden by the opaque `some View` type.\n\nFor instance, assume we were to assign `_render`, the following closure `{ wrapped.render() }`. Do you think Swift would be able to infer the type of what the `render()` method on the wrapped object conforming to `ButtonType` returns? \n\nWell, first off, Swift doesn't know, at compilation time, which concrete strategy object conforming to `ButtonType` is going to be wrapped by `AnyButtonType`; hence, it cannot know what's the _actual_ view being returned by the `render()` method. Therefore, the most the Swift compiler can do is know that the `render()` method is returning a `ViewType` associated type, still not a concrete type (`Text`, `Button`, `VStack`) from which Swift can infer. How does it know? Well, we told that `T` is a type conforming to `ButtonType`, and that's all Swift knows.\n\nThat's the reason why we used the `AnyView` type-erased `View`, and wrapped whichever object conforming to `View`, being returned by the `render()` method on any of the wrapped concrete strategy object — either `Destructive` or `Cancel`, in our case.\n\n```swift\n// Within the `AnyButtonType` wrapper\n\nfileprivate init\u003cT: ButtonType\u003e(_ wrapped: T) {\n  self._render = { AnyView(wrapped.render()) }\n}\n```\n\nWe are allowed to write `AnyView(wrapped.render())`, and Swift won't complain at compilation time, because it knows `AnyView` wraps any object conforming to `View`, and it just so happens that we defined our `ViewType` associated type to conform to `View`. At runtime, Swift will know which object of type `View` we are actually wrapping into `AnyView`.\n\nFinally, we return the result of calling `_render()` from the concrete implementation of `render()` on the `AnyButtonType` struct.\nSince `AnyView` is a concrete type, it's simply inferred by Swift when returned from `render() -\u003e some View`.\n\n```swift\n// Within the `AnyButtonType` wrapper\n\n@ViewBuilder\nfunc render() -\u003e some View {\n  return self._render()\n}\n```\n\nFinally, notice how we wrap our concrete strategies (`Destructive` and `Cancel`) within `AnyButtonType` when returning from the static factory methods to type-erase them:\n\n```swift\n// Define static factory methods: semantic API interface for encapsulating internal implementation details\n      \npublic static func destructive(_ label: Text, _ action: @escaping () -\u003e Void) -\u003e ModalCard.Button {\n    Button(type: AnyButtonType(Destructive(label: label, action: action)))\n}\n\npublic static func cancel(_ action: @escaping () -\u003e Void) -\u003e ModalCard.Button {\n    Button(type: AnyButtonType(Cancel(action: action)))\n}\n```\n\n#### Strategy-like Pattern (via Enum) vs Full Strategy Pattern\n\nLet's end this walk-through on the `ModalCard` component by explaining to you why, for this specific component, I decided to stick with the **Strategy-like** pattern using an internal enum for describing different strategies (rendering logic) based on the case (`.destruvtive` and `.cancel`).\n\nGenerally, the main reason why you would use the Strategy Design Pattern is when your context class/struct starts getting overwhelmed with bulky conditionals that switch the class's behavior depending on a certain property or parameter.\n\nFor instance, take our internal enum `ButtonType`:\n\n```swift\n// Within `ModalCard.Button`\n\nprivate enum ButtonType {\n  case destructive(label: Text, action: () -\u003e Void)\n  case cancel(action: () -\u003e Void)\n}\n```\n\nIf it had had multiple conditionals to switch the context struct's behavior, our code would have been a mess, and every time we wanted to change or expand our behaviors, we would have had to _modify_ the code within `enum`, and that's not a best practice for when you have a large codebase.\n\nInstead, with the classic Strategy pattern, we can create as many classes/structs as we have versions of a certain algorithm/behavior, all conforming to the strategy protocol — `ButtonType`, in our case.\n\nAlso, when using the Strategy pattern, your team can plan for future implementation/changes for your algorithms, because your codebase becomes flexible and easy to update. In our case, we only have two rendering algorithms/actions (`destructive`, and `cancel`), and that's why I decided to stick with an `enum`, instead of using structs to isolate the complexity of my codebase. \n\nHowever, I invite you to think about a scenario when you may have multiple versions of the same algorithm. In such a case, it's not recommended to keep your code in an `enum`, because it becomes cluttered and it's not even open to the Open/Closed principle from SOLID — open to extension, closed to modification; that is, if you were to expand or update your behaviors, you would be forced to _modify_ your code; on the other hand, if you were using the _full_ Strategy Design Pattern, to embed a new version of an algorithm — rendering our `Button` views, in our case — you would just need to create a new struct that adopts the `ButtonType` strategy protocol. Also, if you had to modify an existing behavior, you keep working on that specific struct that isolates that behavior, keeping your code modularized, flexible, and scalable.\n\n#### Usage Example\n\nJust for clarity's sake, I will repost the code snippet that uses my `ModalCard` component:\n\n```swift\n// At the top of your swift file\nimport SwiftUI\nimport ModalCard\n\n// Within the `body` computed property of your view\nModalCard(\n  title: \"Delete Account\",\n  message: \"This action cannot be undone.\",\n  primaryButton: .destructive(\n      Text(\"Delete\"),\n      { print(\"Delete\") }\n  ),\n  secondaryButton: .cancel(\n      { print(\"Cancel\") }\n  )\n)\n```\n\nI hope this guide served you well in walking you through the various facets of building adaptive components, and made you realize how simple interfaces hide quite a bit of complexity.\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsaverio-negro%2Fmodalcard","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsaverio-negro%2Fmodalcard","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsaverio-negro%2Fmodalcard/lists"}