In the next code, I have two views “SelectSymbolView” which contains “SymbolCategorySection”, the issue is in “SymbolCategorySection” which updates “selectedSymbol” four times when the user taps on a symbol to select it even when I ensure to not update “selectedSymbol” until it’s necessary.
The SelectSymbolView:
struct SelectSymbolView: View {
// MARK: - Properties
@Binding var selectedSymbol: String
@State private var selectedCategory: SymbolCategory = .cloths
@State private var dismissView = false
let categories = SymbolCategory.allCases
let symbolGridColumns = [GridItem(.adaptive(minimum: 48, maximum: 48), spacing: 11, alignment: .center)]
@Environment(.dismiss) private var dismiss
// MARK: - Body
var body: some View {
NavigationStack {
ScrollViewReader { scrollProxy in
ScrollView {
LazyVStack(spacing: 18) {
ForEach(categories, id: .self) { category in
SymbolCategorySection(selectedSymbol: $selectedSymbol, category: category)
.id(category.title)
}
.padding(.horizontal, 16)
}
}
.offset(y: 16)
.overlay(alignment: .bottom) {
CategoryTapBar(selectedCategory: $selectedCategory, categories: categories)
.offset(y: -22)
}
.onChange(of: selectedCategory, initial: false) { _, _ in
withAnimation(.smooth(duration: 0.3)) {
scrollProxy.scrollTo(selectedCategory.title, anchor: .top)
}
}
.onChange(of: selectedSymbol, initial: false, { _, _ in
dismissView = true
})
.task(id: selectedSymbol, priority: .userInitiated) {
guard dismissView else { return }
try? await Task.sleep(seconds: 0.7)
dismiss()
}
}
.navigationTitle("Select Symbol")
.navigationBarTitleDisplayMode(.inline)
.background(Color(.systemGroupedBackground))
.interactiveDismissDisabled(true)
.toolbar {
ToolbarItem(placement: .topBarLeading) {
CancelButton(isToolbarItem: true) { dismiss() }
}
}
}
}
}
The SymbolCategorySection view
extension SelectSymbolView {
struct SymbolCategorySection: View {
// MARK: - Properties
@Binding var selectedSymbol: String
let category: SymbolCategory
let symbolGridColumns = [GridItem(.adaptive(minimum: 48, maximum: 48), spacing: 11, alignment: .center)]
// MARK: - Body
var body: some View {
LazyVStack(alignment: .leading, spacing: 10) {
// Section Title
HStack(alignment: .center, spacing: 12) {
Image(systemName: category.symbolName)
.font(.title3)
.fontWeight(.semibold)
.frame(width: 24, height: 24)
Text(category.title)
.font(.title2)
.fontWeight(.bold)
}
.foregroundStyle(Color(.secondaryLabel))
.padding(.horizontal, 8)
// Section Symbols
LazyVGrid(columns: symbolGridColumns, alignment: .center, spacing: 11) {
ForEach(category.symbols, id: .self) { symbolData in
TaskSymbol(systemName: symbolData.systemName,
style: .selection,
color: symbolColor(for: symbolData))
.overlay(alignment: .bottomTrailing, content: {
if symbolData.isPlus {
AppPlus(type: .symbol)
}
})
.onTapGesture {
select(symbolData.systemName)
}
}
}
.padding(.vertical, 9)
.background(SettingItemBackground())
}
.sensoryFeedback(.selection, trigger: selectedSymbol)
.onChange(of: selectedSymbol, initial: false) { oldValue, newValue in
print("🔶 selected Symbol changed") <<< Use it to indicate update times
}
}
// MARK: - Actions
private func select(_ symbol: String) {
if selectedSymbol != symbol {
withAnimation(.smooth(duration: 0.3)) {
selectedSymbol = symbol
}
}
}
private func symbolColor(for symbolData: SymbolData) -> Color {
if selectedSymbol == symbolData.systemName {
return Color(.systemBlue)
}
if symbolData.isPlus {
return Color(.secondaryLabel)
}
return Color(.charcoalGray)
}
}
}