Skip to content

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

Draft
ky-is wants to merge 5 commits intoskiptools:mainfrom
ky-is:buttonrepresentable
Draft

Add ButtonRepresentable/TextRepresentable protocols for rendering Alert/Confirmation Dialog/Menu content#160
ky-is wants to merge 5 commits intoskiptools:mainfrom
ky-is:buttonrepresentable

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

#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

public final class ShareLink : View {
public final class ShareLink : View, ButtonRepresentable {
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

@marcprux
Copy link
Member

marcprux commented Mar 7, 2026

Catching up on the backlog, I would merge this but it needs some conflict resolution to merge with recent changes. I'm going to put this into Draft state until such time as someone can spend time getting it back into mergable state.

@marcprux marcprux marked this pull request as draft March 7, 2026 23:37
public convenience init(item: String, subject: Text? = nil, message: Text? = nil) {
self.init(text: Text(item), subject: subject, message: message) {
Image(systemName: Self.defaultSystemImageName)
Label("Share...", systemImage: Self.defaultSystemImageName)
Copy link
Member

Choose a reason for hiding this comment

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

I think we might be able to use stringResource(android.R.string.share) from androidx.compose.ui.res.stringResource to get free localization here.

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.

3 participants