Search code examples
swiftswiftuialert

Update text inside alert message


I am currently working on a dialog with SwiftUI and I wanted to share the progress of the download inside the alert message. So it's updating the percent text at the end. I have used the old deprecated Alert version and also the new one and I am facing a different behavior on both.

Old deprecated API

.alert(isPresented: $showingAlert) {
   Alert(title: Text("Prepare download"),
         message: Text("The video will be downloaded ... (\(viewModel.formattedProgressInPercent()))"),
         dismissButton: .cancel(Text("Cancel")) {
            viewModel.onCancelDownloadTaped()
         }
   )
}

This is with the new API

.alert("Prepare download", 
   isPresented: $showingAlert, actions: {
      Button("Cancel", role: .cancel) {
         viewModel.onCancelDownloadTaped()
      }
   }, message: {
      Text("The video will be downloaded ... (\(viewModel.formattedProgressInPercent))")
   }
)

The new API does not make it possible to update the text.

Maybe someone has an idea how I can achieve that I can update the text also with the new API, as I am not wanting to use the deprecated version. Thx in advance :-)


Solution

  • The version alert(_:isPresented:presenting:actions:message:) provides another way to present an alert. This one also accepts data to present, but the documentation states:

    The data should not change after the presentation occurs. Any changes that you make after the presentation occurs are ignored.

    So it is perhaps no surprise, that the version without data does not allow dynamic updates either.

    Rather than going back to the deprecated version, another workaround would be to create your own custom alert. You can then show whatever information you like and style it any way you like too.

    • One way to present it would be as the top layer of a ZStack.
    • A full screen layer can be shown behind it, to provide a dimming effect and to capture taps over the background.

    Here is an example that looks and behaves quite like a native alert:

    @State private var showingAlert = false
    @State private var progress = 0
    
    private var progressAlert: some View {
        VStack(spacing: 0) {
            VStack(spacing: 6) {
                Text("Downloading...")
                    .font(.headline)
                Text("Progress: \(progress)")
                    .font(.footnote)
            }
            .padding()
            .frame(minHeight: 80)
    
            Divider()
    
            HStack(spacing: 0) {
                Button("Cancel", role: .cancel) {
                    // ...
                    showingAlert = false
                }
                .fontWeight(.semibold)
                .frame(maxWidth: .infinity)
                Divider()
                Button("Start again", role: .none) {
                    // ...
                    showingAlert = false
                }
                .frame(maxWidth: .infinity)
            }
            .buttonStyle(.borderless)
            .frame(maxHeight: 44)
        }
        .frame(maxWidth: 270)
        .background {
            RoundedRectangle(cornerRadius: 10)
                .fill(.background)
        }
    }
    
    var body: some View {
        ZStack {
    
            // main content
    
            if showingAlert {
                Color.black
                    .opacity(0.2)
                    .ignoresSafeArea()
                    .onTapGesture { showingAlert = false }
                    .transition(.opacity.animation(.easeInOut(duration: 0.2)))
                progressAlert
                    .transition(
                        .scale(0.8).combined(with: .opacity)
                        .animation(.spring(duration: 0.25))
                    )
            }
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
    }
    

    Animation