Create an Interactive Badge List

The Badge View

Here we create a SwiftUI Badge that has the ability to switch between different badge types. Right now it includes a .normal and .removable type, but can be expanded in the future for further customization.

.normal - This specific type may be useful in tagging items in our UI which belong to specific categories or topics.

.removable - This type may be useful when creating a "filter" component for long lists or even a catalog of products for which we want to let the user refine their search.

struct Badge: View {
    
    var name: String
    var color: Color = .blue
    var type: BadgeType = .normal
    
    enum BadgeType {
        case normal
        case removable(()->())
    }
    
    var body: some View {
        HStack{
            // Badge Label
            Text(name)
                .font(Font.caption.bold())
            
            // Add 'x' if removable, and setup tap gesture
            switch type {
                case .removable( let callback):
                    Image(systemName: "xmark")
                        .resizable()
                        .frame(width: 8, height: 8, alignment: .center)
                        .font(Font.caption.bold())
                        .onTapGesture {
                            callback()
                        }
                default:
                    EmptyView()
            }
        }
        .padding(.horizontal, 10)
        .padding(.vertical, 5)
        .background(color)
        .cornerRadius(20)
    }
}

Example use as a Search Filter

The example below utilizes the .removable type to show how the badges may be removed from the view by a user's tap gesture.

struct ContentView: View {
    
    @State var filters: [String] = [
	"SwiftUI", "Programming", "iOS", "Mobile Development", "😎"
    ]
    
    var body: some View {
        ScrollView (.horizontal, showsIndicators: false) {
            HStack {
                ForEach(filters, id: \.self) { filter in
                    Badge(name: filter, color: Color(red: 228/255, green: 237/255, blue: 254/255), type: .removable({
                        withAnimation {
                            self.filters.removeAll { $0 == filter }
                        }
                    }))
                    .transition(.opacity)
                }
            }
            .padding(.horizontal, 20)
        }
    }
}