【问题标题】:Memory leak when using custom text field in SwiftUI在 SwiftUI 中使用自定义文本字段时出现内存泄漏
【发布时间】:2020-08-07 14:40:26
【问题描述】:

我正在使用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 完美配合。

【问题讨论】:

  • 不是很相关,但你可以代替static var currencyFormatter: NumberFormatterstatic let currencyFormatter = { ... return nf }()
  • 这很可能是相关的,因为当前代码在每次生成视图时都在创建和配置一个新的格式化程序,而创建和配置一个格式化程序是一个相当重量级的操作
  • @jrturton 使其全球化仍然会破坏应用程序。发布者和onRecieve 方法进入一个终端循环,锁定主线程并最终崩溃。
  • 它也缺少像 TextField 这样带有格式化程序参数的语言环境更改处理。

标签: swift swiftui


【解决方案1】:

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

使用 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
    }
}

【讨论】:

  • 太棒了-谢谢!!!无论如何要在此代码中添加过滤器以忽略非数字字符?
  • 我不太明白这是如何避免内存循环的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-09
  • 2013-11-02
  • 1970-01-01
  • 1970-01-01
  • 2018-06-28
相关资源
最近更新 更多