79224347

Date: 2024-11-25 19:22:26
Score: 1.5
Natty:
Report link

As per @Sweeper's initial suggestion in the comments, I ended up creating a custom confirmation dialog from scratch, that seeks to emulate the default .confirmationDialog, with some extra customization options.

With this custom confirmation, I have control over the color of the button labels, plus additional control over:

The custom dialog still:

There may be some other features missing when compared to the default, but for my purposes, this is suitable at this stage.

Here's the full code, followed by some example usage and screenshots:

import SwiftUI

struct ConfirmationDialogButtons: View {
    
    //State values
    @State private var showButtonDialog = true
    
    //Body
    var body: some View {
        ZStack {
            VStack {
                Button("Open button dialog") {
                    showButtonDialog.toggle()
                }
            }
            .buttonStyle(.borderedProminent)
            .buttonDialog(
                title: "Some menu title text",
                isPresented: $showButtonDialog,
                labelColor: .green
            ) {
                Button("Action 1") {}
                Button("Action 2") {}
                Button("Action 3") {}
            }
        }
        .tint(.green)
    }
}

extension View {
    func buttonDialog(
        
        title: String = "",
        isPresented: Binding<Bool>,
        
        labelColor: Color? = nil,
        buttonSpacing: CGFloat? = nil,
        buttonBackground: Color? = nil,
        buttonCornerRadius: CGFloat? = nil,
        
        @ViewBuilder buttons: @escaping () -> some View
        
    ) -> some View {
        self
            .modifier(
                ButtonDialogModifier(
                    title: title,
                    isPresented: isPresented,
                    
                    labelColor: labelColor,
                    buttonSpacing: buttonSpacing,
                    buttonBackground: buttonBackground,
                    buttonCornerRadius: buttonCornerRadius,
                    
                    buttons: buttons
                )
            )
    }
}

struct ButtonDialogModifier<Buttons: View>: ViewModifier {
    
    //Parameters
    var title: String
    @Binding var isPresented: Bool
    
    var labelColor: Color
    var buttonSpacing: CGFloat
    var buttonBackground: Color
    var buttonCornerRadius: CGFloat
    var dialogCornerRadius: CGFloat
    
    @ViewBuilder let buttons: () -> Buttons
    
    //Default values
    private let defaultButtonBackground: Color = Color(UIColor.secondarySystemBackground)
    private let defaultCornerRadius: CGFloat = 12
    private var cancelButtonLabelColor: Color
    
    //Initializer
    init(
        title: String? = nil,
        isPresented: Binding<Bool>,
        
        labelColor: Color? = nil,
        buttonSpacing: CGFloat? = nil,
        buttonBackground: Color? = nil,
        buttonCornerRadius: CGFloat? = nil,
        dialogCornerRadius: CGFloat? = nil,
        
        buttons: @escaping () -> Buttons
    ) {
        //Initialize with default values
        self.title = title ?? ""
        self._isPresented = isPresented
        
        self.labelColor = labelColor ?? .accentColor
        self.buttonSpacing = buttonSpacing ?? 0
        self.buttonBackground = buttonBackground ?? defaultButtonBackground
        self.buttonCornerRadius = (buttonCornerRadius != nil ? buttonCornerRadius : self.buttonSpacing == 0 ? 0 : buttonCornerRadius) ?? defaultCornerRadius
        self.dialogCornerRadius = dialogCornerRadius ?? buttonCornerRadius ?? defaultCornerRadius
        
        self.buttons = buttons
        
        self.cancelButtonLabelColor = self.buttonBackground == defaultButtonBackground ? self.labelColor : self.buttonBackground
    }
    
    //Body
    func body(content: Content) -> some View {
        
        content
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .overlay {
                ZStack(alignment: .bottom) {
                    
                    if isPresented {
                        Color.black
                            .opacity(0.2)
                            .ignoresSafeArea()
                            .transition(.opacity)
                    }
                    
                    if isPresented {
                        
                        //Menu wrapper
                        VStack(spacing: 10) {
                            VStack(spacing: buttonSpacing) {
                                Text(title)
                                    .foregroundStyle(.secondary)
                                    .font(.subheadline)
                                    .frame(maxWidth: .infinity, alignment: .center)
                                    .padding()
                                    .background(Color(UIColor.secondarySystemBackground), in: RoundedRectangle(cornerRadius: buttonCornerRadius))
                                
                                // Apply style for each button passed in content
                                buttons()
                                    .buttonStyle(FullWidthButtonStyle(labelColor: labelColor, buttonBackground: buttonBackground, buttonCornerRadius: buttonCornerRadius))
                            }
                            .font(.title3)
                            .clipShape(RoundedRectangle(cornerRadius: dialogCornerRadius))
                            
                            //Cancel button
                            Button {
                                isPresented.toggle()
                            } label: {
                                Text("Cancel")
                                    .fontWeight(.semibold)
                            }
                            .buttonStyle(FullWidthButtonStyle(labelColor: cancelButtonLabelColor, buttonBackground: Color(UIColor.tertiarySystemBackground), buttonCornerRadius: dialogCornerRadius))
                        }
                        .font(.title3)
                        .padding(10)
                        .transition(.move(edge: .bottom))
                    }
                }
                .animation(.easeInOut, value: isPresented)
            }
    }
    
    //Custom full-width button style
    private struct FullWidthButtonStyle: ButtonStyle {
        
        //Parameters
        var labelColor: Color
        var buttonBackground: Color = Color(UIColor.secondarySystemBackground)
        var buttonCornerRadius: CGFloat
        
        //Body
        func makeBody(configuration: Configuration) -> some View {
            configuration.label
                .frame(maxWidth: .infinity) // Make the button full width
                .padding()
                .background(buttonBackground, in: RoundedRectangle(cornerRadius: buttonCornerRadius))
                .opacity(configuration.isPressed ? 0.8 : 1.0) // Add press feedback
                .foregroundStyle(labelColor)
                .overlay(Divider(), alignment: .top)
        }
    }
}


#Preview {
    ConfirmationDialogButtons()
}

Customization examples

Simple color label customization:

.buttonDialog(
    title: "Some menu title text",
    isPresented: $showButtonDialog,
    labelColor: .cyan // <- Simple button label color customization
) {
    Button("Action 1") {}
    Button("Action 2") {}
    Button("Action 3") {}
}

enter image description here

Dark mode support (follow system setting):

enter image description here

Button spacing:

.buttonDialog(
    title: "Some menu title text",
    isPresented: $showButtonDialog,
    labelColor: .cyan, // <- Button label color customization
    buttonSpacing: 10 // <- Button spacing
) {
    Button("Action 1") {}
    Button("Action 2") {}
    Button("Action 3") {}
}

enter image description here

Button corner radius:

.buttonDialog(
    title: "Some menu title text",
    isPresented: $showButtonDialog,
    labelColor: .cyan, // <- Button label color customization
    buttonSpacing: 10, // <- Button spacing
    buttonCornerRadius: 30 // <- Button corner radius
) {
    Button("Action 1") {}
    Button("Action 2") {}
    Button("Action 3") {}
}

enter image description here

Custom button background:

.buttonDialog(
    title: "Some menu title text",
    isPresented: $showButtonDialog,
    labelColor: .white, // <- Button label color customization
    buttonSpacing: 10, // <- Button spacing
    buttonBackground: .green, // <- Button background
    buttonCornerRadius: 30 // <- Button corner radius
) {
    Button("Action 1") {}
    Button("Action 2") {}
    Button("Action 3") {}
}

enter image description here

Reasons:
  • Probably link only (1):
  • Long answer (-1):
  • Has code block (-0.5):
  • User mentioned (1): @Sweeper's
  • Self-answer (0.5):
  • Low reputation (0.5):
Posted by: Andrei G.