Skip to content

Add ButtonRepresentable/TextRepresentable protocols for rendering Alert/Confirmation Dialog/Menu content #160

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

ky-is
Copy link
Contributor

@ky-is ky-is commented Mar 22, 2025

Followup to #150 and #125

ButtonRepresentable protocol

  • Defines a common interface for Views with an action, label, and optional role
  • Conformed to by Button, Link, NavigationLink, ShareLink

TextRepresentable protocol

  • Allows unwrapping Text from Views with a primary Text attribute
  • Conformed to by Label, Text

This simplifies the code a bit and supports more cases for displaying these views in Alert, Confirmation Dialog, and Menu contexts. I've attached a demo view below for testing these changes.

Let me know if this approach looks good!


  • REQUIRED: I have signed the Contributor Agreement
  • REQUIRED: I have tested my change locally with swift test
  • OPTIONAL: I have tested my change on an iOS simulator or device
  • OPTIONAL: I have tested my change on an Android emulator or device

import SwiftUI

struct ContentView: View {
    @State private var buttonSelection: String?
    @State private var navigationString: String?
    @State private var showAlert = false
    @State private var showConfirmationDialog = false

    private let url = URL(string: "https://skip.tools")!

    var body: some View {
        NavigationStack {
            ScrollView {
                Section {
                    VStack {
                        Text("Selected button: \(buttonSelection ?? "nil")")
                        actionButtons(label: "VStack")
                    }
                    .buttonStyle(.bordered)
                }

                Section {
                    VStack {
                        Button("Alert") { showAlert = true }
                            .alert("Alert", isPresented: $showAlert) {
                                actionButtons(label: "Alert")
                            } message: {
                                Label("Alert message in a Label", systemImage: "heart")
                            }
                        Button("Confirmation Dialog") { showConfirmationDialog = true }
                            .confirmationDialog("Confirmation Dialog", isPresented: $showConfirmationDialog) {
                                actionButtons(label: "Alert")
                            } message: {
                                Button("Confirmation message inside a button") {}
                            }
                        Menu("Menu") {
                            actionButtons(label: "Alert")
                        }
                    }
                    .buttonStyle(.borderedProminent)
                }
            }
            .navigationDestination(for: String.self) { navigationString in
                Text("From: \(navigationString)")
            }
        }
    }

    private func actionButtons(label: String) -> some View {
        Group {
            Label("\(label) Label", systemImage: "heart")
            Button("Button") { buttonSelection = label }
            Link("Link", destination: url)
            Link(destination: url) { Label("Link+icon", systemImage: "heart") }
            ShareLink(item: url)
            ShareLink(item: url) { Text("Share") }
            NavigationLink("NavigationLink", value: label)
        }
    }
}

@cla-bot cla-bot bot added the cla-signed label Mar 22, 2025
@ky-is ky-is changed the title Buttonrepresentable Add ButtonRepresentable/TextRepresentable protocols for rendering Alert/Confirmation Dialog/Menu content Mar 22, 2025
@aabewhite
Copy link
Contributor

Awesome! Will take a look when I get through my current task

Copy link
Contributor

@aabewhite aabewhite left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple of comments, but overall looks great

#endif
}

// SKIP @bridge
public init(destination: URL, bridgedLabel: any View) {
#if SKIP
content = Button(bridgedRole: nil, action: { self.openURL(destination) }, bridgedLabel: bridgedLabel)
self.action = { self.openURL(destination) }
self.label = ComposeBuilder(view: bridgedLabel)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It isn't intuitive, but should use ComposeBuilder.from { bridgedLabel }. The "view:" constructor is for when we know the view is something other than another ComposeBuilder

private static let defaultSystemImageName = "square.and.arrow.up"
private static let defaultTitle = "Share..."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should use the literal constructors or make this a LocalizedStringKey, otherwise there's no way to localize this string

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants