SwiftUI Dialogue Box - A simple but highly customizable package that can replace Apple's Alert

SwiftUI Dialogue Box (v0.1)

Features

SwiftUI Dialogue Box is a simple but highly customizable package that can replace Apple’s Alert, Sheet and ConfirmationDialog.

  • attach a dialogueBox-modifier to the top view in your view hiearchy to make sure it will always appear on top
  • pass in a custom header or use OptionDialogueDefaultHeader
  • the type property determines if the box should appear as alert in the middle of the screen or from the bottom edge as sheet.
  • pass in the the default buttons OptionButton or CheckmarkButton or any custom buttons you create
  • create an array of OptionItems or CheckmarkOptionItems in your model and iterate over them in the buttons closure of dialogueBox to create buttons. Or simply create your own model items.
  • Background effects: set a dim or blur effect
  • Check out the example project included in this package for more details.

Code Examples

Simple button dialogue box

public struct SimpleDialogueExample:View  {
    
    @Environment(\.colorScheme) var colorScheme
    @State var show: Bool = false
    @State var dialogueBoxType: DialogueType = .alert
    @State var backgroundEffect: BackgroundEffect = .dim

    @State var items: [OptionItem] = [OptionItem(title: "Edit", subtitle: nil, image: Image(systemName: "pencil.circle"),  shouldDismiss: true, enabled: true, triggerHandler: nil),
          OptionItem(title: "Share", subtitle: "..with everyone", image: Image(systemName: "square.and.arrow.up"),  shouldDismiss: true, enabled: true, triggerHandler: nil),
          OptionItem(title: "Cancel", subtitle: nil, image: nil, tintColor: .red, shouldDismiss: true, enabled: true, triggerHandler: nil)
         ]
    
    public init() {
        
    }
    
    
    public var body: some View {
        
        GeometryReader { proxy in
            VStack {
                VStack {
                    Picker(selection: $dialogueBoxType, label: Text("Dialogue Type")) {
                        Text("Alert").tag(DialogueType.alert)
                        Text("Sheet").tag(DialogueType.sheet)
                    }.pickerStyle(WheelPickerStyle())
                    
                    Picker(selection: $backgroundEffect, label: Text("Background Effect")) {
                        Text("Dim").tag(BackgroundEffect.dim)
                        Text("Blur").tag(BackgroundEffect.blur)
                        Text("None").tag(BackgroundEffect.none)
                    }.pickerStyle(WheelPickerStyle())

                    
                }
                Spacer()
                Divider()
                
                     Button(action: {
                         self.show = true
                     }) {
                         Text(self.dialogueBoxType == .alert ? "Show Alert" : "Show Sheet")
                     }.padding()

                
                Spacer()
            }
            .dialogueBox(type: dialogueBoxType, frameWidth: dialogueBoxType == .alert ?  min(proxy.size.width * 0.75, 300) : min(400, proxy.size.width),  settings: OptionDialogueSettings(backgroundEffect: backgroundEffect, boxShadow: shadow, dismissOnBackgroundTap: self.dialogueBoxType == .sheet), show: $show, header: {
                  OptionDialogueDefaultHeader(title: "Simple Dialogue", message: "Choose an option")
            }) {

               dialogueBoxContent()

            }
      
        }
    }
    
    public var shadow: Shadow {
        let color = colorScheme == .light ? Color.gray.opacity(0.4) : Color.clear
        let offsetX: CGFloat = self.dialogueBoxType == .alert ? -5 : 0
        return Shadow(color: color, radius: 5, x: offsetX, y: -5)

    }
    
    // add included button or create your own
    @ViewBuilder func dialogueBoxContent()->some View {

            ForEach(items) { item in
                
                OptionButton(title: item.title, subtitle: item.subtitle, accentColor: item.tintColor, image: item.image,  triggerHandler: item.triggerHandler, shouldDismiss: item.shouldDismiss, enabled:  .constant(item.enabled), show: $show)
                 
            }
        
       
    }
    

}

Checkmark dialogue box

public struct CheckmarkExample:View  {
    @Environment(\.colorScheme) var colorScheme
    @StateObject  var viewModel = CheckmarkItemsViewModel()  // check out the example project for details
    
   public init() {}
    
    public var body: some View {
        
        GeometryReader { proxy in
            VStack {
                VStack {
                    Picker(selection: $viewModel.dialogueBoxType, label: Text("Dialogue Type")) {
                        Text("Alert").tag(DialogueType.alert)
                        Text("Sheet").tag(DialogueType.sheet)
                    }.pickerStyle(WheelPickerStyle())
                    
                    Picker(selection: $viewModel.backgroundEffect, label: Text("Background Effect")) {
                        Text("Dim").tag(BackgroundEffect.dim)
                        Text("Blur").tag(BackgroundEffect.blur)
                        Text("None").tag(BackgroundEffect.none)
                    }.pickerStyle(WheelPickerStyle())
                    
                    Toggle("Checkmark Multi-Select", isOn: $viewModel.multiSelect).padding()
                    
                }
                Spacer()
                Divider()
 
                    Button(action: {
                        self.viewModel.showCheckmarkDialogue = true
                    }) {
                        Text("Show Checkmark Dialogue")
                    }.padding()
                
                Spacer()
            }

            .dialogueBox(type: viewModel.dialogueBoxType, frameWidth: viewModel.dialogueBoxType == .alert ?  min(proxy.size.width * 0.75, 300) : min(400, proxy.size.width),  settings: OptionDialogueSettings(backgroundEffect: viewModel.backgroundEffect, boxShadow: shadow, dismissOnBackgroundTap: self.viewModel.dialogueBoxType == .sheet), show: $viewModel.showCheckmarkDialogue, header: {
                OptionDialogueDefaultHeader(title: "Checkmark Dialogue", message: "Select at least one option")
            }) {

                dialogueBoxContent


            }
            .onChange(of: self.viewModel.multiSelect) { _ in
                self.viewModel.createCheckmarkItems()
            }
            
               
        }
 
    }
    
    var shadow: Shadow {
        let color = colorScheme == .light ? Color.gray.opacity(0.4) : Color.clear
        let offsetX: CGFloat = self.viewModel.dialogueBoxType == .alert ? -5 : 0
        return Shadow(color: color, radius: 5, x: offsetX, y: -5)

    }
    
    @ViewBuilder public var dialogueBoxContent: some View {

        ForEach(viewModel.checkmarkItems) { item in
                
            CheckmarkButton(checkmarkItem: item, allCheckmarkItems: self.viewModel.checkmarkItems, show:  $viewModel.showCheckmarkDialogue, multiSelect: viewModel.multiSelect) {
                self.viewModel.updateMultiSelectOKButtonState()
            }
           
        }
        if viewModel.multiSelect {
            OptionButton(title: "OK", subtitle: nil, image: nil , triggerHandler: nil, shouldDismiss: true, enabled: $viewModel.multiSelectOKButtonEnabled, show: $viewModel.showCheckmarkDialogue)
            
            
            OptionButton(title: "Cancel", subtitle: nil, accentColor: .red, image: nil,  triggerHandler: nil, shouldDismiss: true, enabled: .constant(true), show: $viewModel.showCheckmarkDialogue)
              
        }
        
       
    }
    

    


}

Change log

Version 0.1.1

Minor improvements: Added bottom area inset padding underneath the buttons of a sheet. triggerHandler is now the last parameter of OptionButton initialiser.

Version 0.1

Initial public release.

Author

dominikbutz@gmail.com

License

SwiftUIGraphs is available under the MIT license. See the LICENSE file for more info.

GitHub

View Github