Skip to content

[WCiOS17] Update .onchange() usage to iOS17 API #16002

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ private struct AnimatedTransitionContainer<Content: View, ID: Equatable>: View {
.onAppear {
updateSize(to: proxy.size.height)
}
.onChange(of: proxy.size) { newSize in
.onChange(of: proxy.size) { _, newSize in
updateSize(to: newSize.height)
}
}
Expand All @@ -186,7 +186,7 @@ private struct AnimatedTransitionContainer<Content: View, ID: Equatable>: View {
.onAppear {
hasAppeared = true
}
.onChange(of: contentID) { newID in
.onChange(of: contentID) { _, newID in
guard newID != previousID else { return }

if hasAppeared {
Expand Down
12 changes: 6 additions & 6 deletions WooCommerce/Classes/POS/Presentation/CartView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -276,11 +276,11 @@ private struct CartScrollViewContent: View {
.onAppear {
updateItemImageVisibility(cartListWidth: geometry.size.width)
}
.onChange(of: geometry.size.width) {
updateItemImageVisibility(cartListWidth: $0)
.onChange(of: geometry.size.width) { _, newValue in
Copy link
Contributor Author

@iamgabrielma iamgabrielma Aug 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passing it implicitly as $0 is no longer something we can do, since we're using the new value inside the closure, we need to be declare it explicitly in .onChange before using it.

updateItemImageVisibility(cartListWidth: newValue)
}
.onChange(of: dynamicTypeSize) {
updateItemImageVisibility(dynamicTypeSize: $0, cartListWidth: geometry.size.width)
.onChange(of: dynamicTypeSize) { _, newValue in
updateItemImageVisibility(dynamicTypeSize: newValue, cartListWidth: geometry.size.width)
}
})
.onPreferenceChange(ScrollOffSetPreferenceKey.self) { position in
Expand All @@ -303,7 +303,7 @@ private struct CartScrollViewContent: View {
scrollViewHeight = height
}
.coordinateSpace(name: Constants.scrollViewCoordinateSpaceIdentifier)
.onChange(of: posModel.cart.purchasableItems.first?.id) { itemToScrollTo in
.onChange(of: posModel.cart.purchasableItems.first?.id) { _, itemToScrollTo in
if posModel.orderStage == .building {
withAnimation {
proxy.scrollTo(itemToScrollTo)
Expand Down Expand Up @@ -405,7 +405,7 @@ private struct PurchasableItemsCartSection: View {
.transition(.opacity)
.accessibilityFocused($accessibilityFocusedItem, equals: cartItem.id)
}
.onChange(of: posModel.cart.accessibilityFocusedItemID) { itemID in
.onChange(of: posModel.cart.accessibilityFocusedItemID) { _, itemID in
if let itemID = itemID {
Task { @MainActor in
accessibilityFocusedItem = itemID
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ struct InfiniteScrollView<Content: View>: View {
.background(
GeometryReader { proxy in
Color.clear
.onChange(of: proxy.frame(in: .named(Constants.scrollViewNamespace)).maxY) { maxY in
.onChange(of: proxy.frame(in: .named(Constants.scrollViewNamespace)).maxY) { _, maxY in
let contentHeight = proxy.size.height
let scrollPosition = contentHeight - maxY

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ struct PointOfSaleCollectCashView: View {
await submitCashAmount()
}
}
.onChange(of: textFieldViewModel.amount) { newValue in
.onChange(of: textFieldViewModel.amount) { _, newValue in
textFieldAmountInput = newValue
updateChangeDueMessage()
}
Expand Down Expand Up @@ -100,7 +100,7 @@ struct PointOfSaleCollectCashView: View {
.frame(minHeight: geometry.size.height)
.animation(.easeInOut, value: errorMessage)
.animation(.easeInOut, value: changeDueMessage != nil)
.onChange(of: textFieldAmountInput) { _ in
.onChange(of: textFieldAmountInput) {
errorMessage = nil
}
.onReceive(Publishers.keyboardFrame) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ struct POSModalViewModifier<Item: Identifiable & Equatable, ModalContent: View>:

func body(content: Content) -> some View {
content
.onChange(of: item) { newItem in
.onChange(of: item) { _, newItem in
if let newItem = newItem {
modalManager.present(onDismiss: {
// Internal dismissal, i.e. from tapping the background
Expand All @@ -106,7 +106,7 @@ struct POSModalViewModifierForBool<ModalContent: View>: ViewModifier {

func body(content: Content) -> some View {
content
.onChange(of: isPresented) { newValue in
.onChange(of: isPresented) { _, newValue in
if newValue {
modalManager.present(onDismiss: {
// Internal dismissal, i.e. from tapping the background
Expand Down Expand Up @@ -174,7 +174,7 @@ struct POSInteractiveDismissModifier: ViewModifier {

func body(content: Content) -> some View {
content
.onChange(of: disabled) { newValue in
.onChange(of: disabled) { _, newValue in
modalManager.setInteractiveDismissal(!newValue)
}
.onAppear {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ struct POSSendReceiptView: View {
.padding(.bottom, keyboardFrame.height)
}
.animation(.easeInOut, value: errorMessage)
.onChange(of: textFieldInput) { _ in
.onChange(of: textFieldInput) {
errorMessage = nil
}
.onReceive(Publishers.keyboardFrame) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ struct POSSheetViewModifier<SheetContent: View>: ViewModifier {
func body(content: Content) -> some View {
content
.sheet(isPresented: $isPresented, onDismiss: onDismiss, content: sheetContent)
.onChange(of: isPresented) { newValue in
.onChange(of: isPresented) { _, newValue in
if newValue {
sheetManager.registerSheetPresented(id: sheetId)
} else {
Expand All @@ -62,7 +62,7 @@ struct POSSheetViewModifierForItem<Item: Identifiable & Equatable, SheetContent:
func body(content: Content) -> some View {
content
.sheet(item: $item, onDismiss: onDismiss, content: sheetContent)
.onChange(of: item) { newItem in
.onChange(of: item) { _, newItem in
let newValue = newItem != nil
if newValue {
sheetManager.registerSheetPresented(id: sheetId)
Expand Down
4 changes: 3 additions & 1 deletion WooCommerce/Classes/POS/Presentation/TotalsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ struct TotalsView: View {
.onAppear {
isShowingTotalsFields = shouldShowTotalsFields
}
.onChange(of: shouldShowTotalsFields, perform: hideTotalsFieldsWithDelay)
.onChange(of: shouldShowTotalsFields) {
hideTotalsFieldsWithDelay(shouldShowTotalsFields)
}
.geometryGroup()
}

Expand Down
2 changes: 1 addition & 1 deletion WooCommerce/Classes/POS/TabBar/POSIneligibleView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ struct POSIneligibleView: View {
.onAppear {
ServiceLocator.analytics.track(event: .PointOfSaleIneligibleUI.ineligibleUIShown(reason: reason))
}
.onChange(of: reason) { newReason in
.onChange(of: reason) { _, newReason in
ServiceLocator.analytics.track(event: .PointOfSaleIneligibleUI.ineligibleUIShown(reason: newReason))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ struct NoticeModifier: ViewModifier {
performClearNoticeTask()
})
)
.onChange(of: notice) { _ in
.onChange(of: notice) {
provideHapticFeedbackIfNecessary(notice.feedbackType)
dispatchClearNoticeTask()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ struct CouponExpiryDateView: View {
.environment(\.timeZone, timezone)
.datePickerStyle(GraphicalDatePickerStyle())
.padding(.vertical, Constants.datePickerVerticalMargin)
.onChange(of: date) { _ in
.onChange(of: date) {
isRemovalEnabled = true
}
VStack {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ struct CustomFieldEditorView: View {
Text(Localization.editorPickerHTML).tag(true)
}
.pickerStyle(.segmented)
.onChange(of: showRichTextEditor) { newValue in
.onChange(of: showRichTextEditor) { _, newValue in
viewModel.trackEditorPickerTapped(showRichTextEditor: newValue)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ struct TapToPayEducationView: View {
}
}
.interactiveDismissDisabled(viewModel.isInteractiveDismissDisabled)
.onChange(of: viewModel.dismiss) { _ in
.onChange(of: viewModel.dismiss) {
dismiss()
}
.onAppear {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ struct SystemStatusReportView: View {
primaryButton: .default(Text(Localization.tryAgainButton), action: viewModel.fetchReport),
secondaryButton: .default(Text(Localization.cancelButton), action: dismissAction))
}
.onChange(of: viewModel.errorFetchingReport) { newValue in
.onChange(of: viewModel.errorFetchingReport) { _, newValue in
showingErrorAlert = newValue
}
.toolbar {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ private extension AddCustomAmountPercentageView {
text: $text,
prompt: Text("0").foregroundColor(Color(.textSubtle))
)
.onChange(of: text, perform: onChangeText)
.onChange(of: text) {
onChangeText(text)
}
.focused()
.focused($focusPercentageInput)
.keyboardType(.decimalPad)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ struct OrderForm: View {
.onAppear {
updateSelectionSyncApproach(for: presentationStyle)
}
.onChange(of: horizontalSizeClass) { _ in
.onChange(of: horizontalSizeClass) {
viewModel.saveInFlightOrderNotes()
viewModel.saveInflightCustomerDetails()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,9 @@ private extension DiscountLineDetailsView {

var body: some View {
TextField(placeholder, text: $text)
.onChange(of: text, perform: onChangeText)
.onChange(of: text) {
onChangeText(text)
}
.focused()
.keyboardType(.numbersAndPunctuation)
.frame(maxWidth: .infinity, minHeight: Layout.rowHeight)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ struct FilterListSelector<ViewModel: FilterListSelectorViewModelable>: View {
VStack(spacing: 0) {
SearchHeader(text: $searchTerm, placeholder: viewModel.filterPlaceholder)
.background(Color(.listForeground(modal: false)))
.onChange(of: searchTerm) { newValue in
.onChange(of: searchTerm) { _, newValue in
viewModel.searchTerm = newValue
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ struct ReceiptEmailView: View {
})
}
}
.onChange(of: viewModel.state) { state in
if state == .success {
.onChange(of: viewModel.state) { _, newState in
if newState == .success {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.6) {
dismiss()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,9 @@ struct ProductSelectorView: View {
viewModel.onLoadTrigger.send()
updateSyncApproach(for: horizontalSizeClass)
}
.onChange(of: horizontalSizeClass, perform: { newSizeClass in
.onChange(of: horizontalSizeClass) { _, newSizeClass in
updateSyncApproach(for: newSizeClass)
})
}
// On the order form, this is not connected; the OrderForm displays the notices.
.notice($viewModel.notice, autoDismiss: false)
.sheet(isPresented: $showingFilters) {
Expand Down Expand Up @@ -319,7 +319,7 @@ private extension ProductSelectorView {
.onAppear(perform: {
adjustViewWidthIfNeeded(using: geometry.size.width)
})
.onChange(of: geometry.size.width) { newViewWidth in
.onChange(of: geometry.size.width) { _, newViewWidth in
adjustViewWidthIfNeeded(using: newViewWidth)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ struct ProductStepper: View {
.multilineTextAlignment(.center)
.fixedSize(horizontal: true, vertical: false)
.focused($textFieldFocused)
.onChange(of: textFieldFocused) { newValue in
.onChange(of: textFieldFocused) { _, newValue in
// We may have unsaved changes in the text field, if switching focus to another text field.
if newValue == false {
viewModel.resetEnteredQuantity()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,11 @@ struct ExpandableBottomSheet<AlwaysVisibleContent, ExpandableContent>: View wher
}
}
.trackSize(size: $expandingContentSize)
.onChange(of: expandingContentSize, perform: { _ in
.onChange(of: expandingContentSize) {
withAnimation {
panelHeight = calculateHeight()
}
})
}
}
.scrollVerticallyIfNeeded()

Expand All @@ -79,7 +79,7 @@ struct ExpandableBottomSheet<AlwaysVisibleContent, ExpandableContent>: View wher
// Always visible content
alwaysVisibleContent()
.trackSize(size: $fixedContentSize)
.onChange(of: fixedContentSize, perform: { [fixedContentSize] _ in
.onChange(of: fixedContentSize) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't seem we need the capture list here: .onChange(of: fixedContentSize) already re-runs the closure every time fixedContentSize changes, so capturing it when created serves no purpose as it will always have the latest value when the closure runs anyway

guard fixedContentSize.width > 0,
fixedContentSize.height > 0 else {
// No animation for initial load
Expand All @@ -88,7 +88,7 @@ struct ExpandableBottomSheet<AlwaysVisibleContent, ExpandableContent>: View wher
withAnimation {
panelHeight = calculateHeight()
}
})
}
.contentShape(Rectangle())
.onTapGesture {
withAnimation {
Expand All @@ -104,25 +104,24 @@ struct ExpandableBottomSheet<AlwaysVisibleContent, ExpandableContent>: View wher
panelHeight = calculateHeight()
}
})
.onChange(of: geometryProxy.size.height,
perform: { newValue in
.onChange(of: geometryProxy.size.height) { _, newValue in
if !isDragging {
DispatchQueue.main.async {
withAnimation {
panelHeight = calculateHeight()
}
}
}
})
}
})
.onChange(of: isExpanded, perform: { newValue in
.onChange(of: isExpanded) { _, newValue in
onChangeOfExpansion?(newValue)
DispatchQueue.main.async {
withAnimation {
panelHeight = calculateHeight()
}
}
})
}
.frame(maxWidth: .infinity, maxHeight: panelHeight, alignment: .bottom)
.background(Color(.listForeground(modal: false)), ignoresSafeAreaEdges: .vertical)
.cornerRadius(Layout.sheetCornerRadius)
Expand Down Expand Up @@ -220,7 +219,7 @@ struct SizeTracker: ViewModifier {
.onAppear {
self.size = proxy.size
}
.onChange(of: proxy.size) { newSize in
.onChange(of: proxy.size) { _, newSize in
self.size = newSize
}
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ struct FeedbackView: View {
configuration.backgroundColor
.cornerRadius(Layout.cornerRadius)
)
.onChange(of: vote) { newValue in
.onChange(of: vote) { _, newValue in
if let newValue {
configuration.onVote(newValue)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ struct ModalOverlay<OverlayContent: View>: View {
.animation(.easeInOut(duration: 0.25), value: internalIsPresented)
}
}
.onChange(of: isPresented) { newValue in
.onChange(of: isPresented) { _, newValue in
withAnimation {
internalIsPresented = newValue
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ struct MultiSelectionList<T: Hashable & Identifiable>: View {
}
.listStyle(.grouped)
}
.onChange(of: query) { value in
.onChange(of: query) { _, value in
onQueryChanged?(value)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,9 @@ struct TitleAndTextFieldRow: View {
HStack {
TextField(placeholder, text: $text, onEditingChanged: onEditingChanged ?? { _ in })
.foregroundColor(contentColor)
.onChange(of: text, perform: { newValue in
.onChange(of: text) { _, newValue in
text = formatText(newValue)
})
}
.onAppear {
text = formatText(text)
}
Expand Down