iosswiftflutterswiftuimobile

SwiftUI view adding trailing and leading icons


What would be the code to add leading and trailing icons to the swift UI view, which I display with the view controller, as displayed on the picture with the close icon and the Clear button?

Is this a built-in behavior, or a custom stack view? Can this be made sticky to the top, so that it would overlay the content if it gets scrolled?

enter image description here

Here is my code for the view:

struct CustomSheetView: View {
    @Environment(\.presentationMode) var presentationMode

    @State private var isNewInMenuSelected = false
    @State private var isOurFavoritesSelected = false
    @State private var isCustomerPickSelected = false

    var filtersLabel: String
    var useOurSearchFiltersLabel: String
    var applyFiltersLabel: String

    var body: some View {
        VStack(alignment: .leading, spacing: 20) {
            Text(filtersLabel)
                .font(.largeTitle)
                .fontWeight(.bold)
                .padding(.top, 20)
                .padding(.leading, 20)

            Text(useOurSearchFiltersLabel)
                .font(.subheadline)
                .fontWeight(.medium)
                .foregroundColor(.secondary)
                .padding(.leading, 20)

            VStack(alignment: .leading, spacing: 10) {
                Toggle("New in menu", isOn: $isNewInMenuSelected)
                Toggle("Our favorites", isOn: $isOurFavoritesSelected)
                Toggle("Customer pick", isOn: $isCustomerPickSelected)
            }
            .padding(.horizontal, 20)

            Spacer()

            Button(action: {
                let checkboxStates = [
                    "new_in_menu": isNewInMenuSelected,
                    "our_favorites": isOurFavoritesSelected,
                    "customer_pick": isCustomerPickSelected
                ]

                let keyWindow = UIApplication.shared.connectedScenes
                    .filter { $0.activationState == .foregroundActive }
                    .compactMap { $0 as? UIWindowScene }
                    .flatMap { $0.windows }
                    .first { $0.isKeyWindow }

                if let flutterViewController = keyWindow?.rootViewController as? FlutterViewController {
                    let channel = FlutterMethodChannel(name: "custom_sheet_channel", binaryMessenger: flutterViewController.binaryMessenger)
                    channel.invokeMethod("checkboxStates", arguments: checkboxStates) { result in
                        // Handle any response if necessary
                    }
                }

                presentationMode.wrappedValue.dismiss()
            }) {
                Text(applyFiltersLabel)
                    .padding()
                    .frame(maxWidth: .infinity)
                    .background(Color(hex: "#d0a771"))
                    .foregroundColor(.white)
                    .cornerRadius(8)
                    .padding(.horizontal, 20)
            }
            .padding(.bottom, 20)
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) // Align content to top-left
    }
}

Solution

  • I assume this question is about implementing a header for a view shown as a .sheet.

    Here are two techniques you can use:

    1. Add a toolbar

    You can apply a toolbar to the sheet content. This means nesting the sheet content inside some kind of container that supports a toolbar, for example, a NavigationStack:

    struct SimpleSheetView: View {
        @Environment(\.dismiss) var dismiss
    
        var body: some View {
            NavigationStack {
                ScrollView {
                    Color.red.frame(height: 200)
                    Color.orange.frame(height: 200)
                    Color.yellow.frame(height: 200)
                    Color.green.frame(height: 200)
                    Color.blue.frame(height: 200)
                    Color.indigo.frame(height: 200)
                    Color.purple.frame(height: 200)
                }
                .navigationTitle("Filters")
                .navigationBarTitleDisplayMode(.inline)
                .toolbar {
                    ToolbarItem(placement: .navigationBarLeading) {
                        Button {
                            dismiss()
                        } label: {
                            Image(systemName: "xmark")
                                .imageScale(.large)
                                .foregroundStyle(.foreground)
                        }
                    }
                    ToolbarItem(placement: .navigationBarTrailing) {
                        Button("Clear") {}
                            .buttonStyle(.plain)
                            .fontWeight(.bold)
                            .foregroundStyle(.green)
                    }
                }
            }
        }
    }
    

    When the main content is scrolled, it goes behind the toolbar and the toolbar automatically takes on a translucent background:

    Screenshot

    2. A custom header

    If you don't especially need the translucent effect behind the header, then it is simple to build a custom header with a plain background.

    var body: some View {
        VStack(spacing: 0) {
            ZStack {
                Text("Filters")
                    .font(.headline)
                    .padding(.vertical, 20)
                HStack {
                    Button {
                        dismiss()
                    } label: {
                        Image(systemName: "xmark")
                            .imageScale(.large)
                            .foregroundStyle(.foreground)
                    }
                    Spacer()
                    Button("Clear") {}
                        .buttonStyle(.plain)
                        .fontWeight(.bold)
                        .foregroundStyle(.green)
                }
                .padding(.horizontal, 20)
            }
            ScrollView {
                // ...
            }
        }
    }
    

    Screenshot