代码之家  ›  专栏  ›  技术社区  ›  Cesare

在SwiftUI中使用自定义文本字段时出现内存泄漏

  •  0
  • Cesare  · 技术社区  · 4 年前

    我用的是 DecimalField 在我的应用程序中放置文本字段。但是,如果我将它与环境对象一起使用,应用程序会因内存泄漏而冻结。

    这是我的模型:

    class PaymentPlan: ObservableObject {
        @Published var amountBorrowed: Decimal?
    }
    

    这是我的内容视图:

    var currencyFormatter: NumberFormatter {
        let nf = NumberFormatter()
        nf.numberStyle = .currency
        nf.isLenient = true
        return nf
    }
    
    struct ContentView: View {
        
        @EnvironmentObject var paymentPlan: PaymentPlan
            
        static var currencyFormatter: NumberFormatter {
            let nf = NumberFormatter()
            nf.numberStyle = .currency
            nf.isLenient = true
            return nf
        }
        
        var body: some View {
            DecimalField("Placeholder", value: $paymentPlan.amountBorrowed, formatter: Self.currencyFormatter)
        }
    }
    

    这是我正在使用的自定义文本字段(源代码):

    import SwiftUI
    import Combine
    
    struct DecimalField : View {
        let label: LocalizedStringKey
        @Binding var value: Decimal?
        let formatter: NumberFormatter
        let onEditingChanged: (Bool) -> Void
        let onCommit: () -> Void
    
        // The text shown by the wrapped TextField. This is also the "source of
        // truth" for the `value`.
        @State private var textValue: String = ""
    
        // When the view loads, `textValue` is not synced with `value`.
        // This flag ensures we don't try to get a `value` out of `textValue`
        // before the view is fully initialized.
        @State private var hasInitialTextValue = false
    
        init(
            _ label: LocalizedStringKey,
            value: Binding<Decimal?>,
            formatter: NumberFormatter,
            onEditingChanged: @escaping (Bool) -> Void = { _ in },
            onCommit: @escaping () -> Void = {}
        ) {
            self.label = label
            _value = value
            self.formatter = formatter
            self.onEditingChanged = onEditingChanged
            self.onCommit = onCommit
        }
    
        var body: some View {
            TextField(label, text: $textValue, onEditingChanged: { isInFocus in
                // When the field is in focus we replace the field's contents
                // with a plain unformatted number. When not in focus, the field
                // is treated as a label and shows the formatted value.
                if isInFocus {
                    self.textValue = self.value?.description ?? ""
                } else {
                    let f = self.formatter
                    let newValue = f.number(from: self.textValue)?.decimalValue
                    self.textValue = f.string(for: newValue) ?? ""
                }
                self.onEditingChanged(isInFocus)
            }, onCommit: {
                self.onCommit()
            })
                .onReceive(Just(textValue)) {
                    guard self.hasInitialTextValue else {
                        // We don't have a usable `textValue` yet -- bail out.
                        return
                    }
                    // This is the only place we update `value`.
                    self.value = self.formatter.number(from: $0)?.decimalValue
            }
            .onAppear(){ // Otherwise textfield is empty when view appears
                self.hasInitialTextValue = true
                // Any `textValue` from this point on is considered valid and
                // should be synced with `value`.
                if let value = self.value {
                    // Synchronize `textValue` with `value`; can't be done earlier
                    self.textValue = self.formatter.string(from: NSDecimalNumber(decimal: value)) ?? ""
                }
            }
            .keyboardType(.decimalPad)
        }
    }
    

    有什么不好的建议吗?文本字段与@State完美结合。

    0 回复  |  直到 4 年前
        1
  •  4
  •   Asperi    4 年前

    这里是固定的部分-为了避免循环它只需要更新真正的新值

    使用Xcode 12/iOS 14测试

    .onReceive(Just(textValue)) {
        guard self.hasInitialTextValue else {
            // We don't have a usable `textValue` yet -- bail out.
            return
        }
        // This is the only place we update `value`.
        let newValue = self.formatter.number(from: $0)?.decimalValue
        if newValue != self.value {
            self.value = newValue
        }
    }