stripe-ios
stripe-ios copied to clipboard
SwiftUI redrawing view when payment sheet is presented and on sheet dismissed completion block is written out inline causing leak.
Summary
When updating/redrawing view when payment sheet is presented and on sheet dismissed completion block is written out. Causes payment sheet to be dismissed and presented quickly than when you close payment sheet, grey overlay stays and memory starts to leak CPU and memory continues to climb until app crashs.
Code to reproduce
When onSheetDismissed is written like this it works as intended
PaymentSheet.FlowController.PaymentOptionsButton(
paymentSheetFlowController: paymentSheetFlowController,
onSheetDismissed: model.onOptionsCompletion) {
ExamplePaymentOptionView(
paymentOptionDisplayData: paymentSheetFlowController.paymentOption)
}
When onSheetDismissed completion block is written inline and view is updated while payment sheet is presented causes undesired behavior
PaymentSheet.FlowController.PaymentOptionsButton(
paymentSheetFlowController: paymentSheetFlowController,
onSheetDismissed: {
model.onOptionsCompletion()
}
) {
ExamplePaymentOptionView(
paymentOptionDisplayData: paymentSheetFlowController.paymentOption)
}
Full Code including the banner view that will update/redraw the view when it becomes dismissed which causes leak
struct ExampleSwiftUICustomPaymentFlow: View {
@StateObject var model = MyCustomBackendModel()
@State var isConfirmingPayment = false
var body: some View {
VStack {
if model.showBanner{
Text("make entire view reload")
}
if let paymentSheetFlowController = model.paymentSheetFlowController {
PaymentSheet.FlowController.PaymentOptionsButton(
paymentSheetFlowController: paymentSheetFlowController,
onSheetDismissed: {
model.onOptionsCompletion()
}
) {
ExamplePaymentOptionView(
paymentOptionDisplayData: paymentSheetFlowController.paymentOption)
}
Button(action: {
// If you need to update the PaymentIntent's amount, you should do it here and
// set the `isConfirmingPayment` binding after your update completes.
isConfirmingPayment = true
}) {
if isConfirmingPayment {
ExampleLoadingView()
} else {
ExamplePaymentButtonView()
}
}.paymentConfirmationSheet(
isConfirming: $isConfirmingPayment,
paymentSheetFlowController: paymentSheetFlowController,
onCompletion: model.onCompletion
)
.disabled(paymentSheetFlowController.paymentOption == nil || isConfirmingPayment)
} else {
ExampleLoadingView()
}
Button {
model.showBanner = true
} label: {
Text("Test Banner")
}
if let result = model.paymentResult {
ExamplePaymentStatusView(result: result)
}
}.onAppear { model.preparePaymentSheet() }.banner(data: .constant(BannerData(title: "Test", detail: "Test", bannerType: .Error)), show: $model.showBanner)
}
}
Banner Code:
extension View {
func banner(data: Binding<BannerData>, show: Binding<Bool>) -> some View {
self.modifier(BannerModifier(bannerData: data, show: show))
}
}
struct BannerData {
var title:String
var detail:String
var bannerType: BannerType
}
enum BannerType {
case Info
case Warning
case Success
case Error
var tintColor: Color {
switch self {
case .Info:
return Color(red: 67/255, green: 154/255, blue: 215/255)
case .Success:
return Color.green
case .Warning:
return Color.yellow
case .Error:
return Color.red
}
}
}
struct BannerModifier: ViewModifier{
@Binding var bannerData:BannerData
@Binding var show:Bool
func body(content: Content) -> some View {
ZStack {
content
if show {
VStack {
HStack {
VStack(alignment: .leading, spacing: 2) {
Text(bannerData.title)
.bold()
Text(bannerData.detail)
.font(Font.system(size: 15, weight: Font.Weight.light, design: Font.Design.default))
}
Spacer()
}
.foregroundColor(Color.white)
.padding(12)
.background(bannerData.bannerType.tintColor)
.cornerRadius(8)
Spacer()
}.zIndex(2.0)
.padding()
.animation(.easeInOut)
.transition(AnyTransition.move(edge: .top).combined(with: .opacity))
.onTapGesture {
withAnimation {
self.show = false
}
}.onAppear(perform: {
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
withAnimation {
self.show = false
}
}
})
}
}
}
}
iOS version
Tested on IOS 15 and IOS 14.4 (same behavior on both)
Installation method
Swift Package Manager
SDK version
21.9.0
Other information
Example Video: https://user-images.githubusercontent.com/11301401/142065214-34b0534c-1e32-4133-b196-50c0af89e35d.MP4
Thanks for filing this! We've been investigating a few other issues with SwiftUI redrawing, so we'll track this along with those.