I've just started learning SwiftUI and I use Date Picker via UIViewRepresentable and I'm injection binding into it because I want to update my date property in View Model. When I open memory graph I see there is few instances of that view model so it causes memory leak. But when I comment Binding property in Date picker there is no memory leak. Does anyone know how to inject binding without memory issues? Here is the code:
Date Picker class (UIViewRepresentable)
struct DatePickerTextField: UIViewRepresentable {
private let textField = BaseTextField()
private let datePicker = UIDatePicker()
private let helper = Helper()
public var typeOfDatePicker: TypeOfDatePicker
public var placeholder: String
@Binding public var date: String
@Binding var dateLimit: String
func setDate() {
let dateVar = DateHelper.getFullDate.string(from: datePicker.date)
switch typeOfDatePicker {
case .startDate:
if let endDate = DateHelper.createDateFromString(dateLimit) {
if datePicker.date <= endDate {
date = dateVar
}
} else {
date = dateVar
}
case .endDate:
if let startDate = DateHelper.createDateFromString(dateLimit) {
if datePicker.date >= startDate {
date = dateVar
}
} else {
date = dateVar
}
}
setDatePickerLimits()
}
func setDatePickerLimits() {
switch typeOfDatePicker {
case .startDate:
datePicker.minimumDate = Calendar.current.date(byAdding: .year, value: -1, to: Date())
if dateLimit != "" {
datePicker.maximumDate = DateHelper.createDateFromString(dateLimit)
} else {
datePicker.maximumDate = Calendar.current.date(byAdding: .year, value: 1, to: Date())
}
case .endDate:
if dateLimit != "" {
datePicker.minimumDate = DateHelper.createDateFromString(dateLimit)
} else {
datePicker.minimumDate = Calendar.current.date(byAdding: .year, value: -1, to: Date())
}
datePicker.maximumDate = Calendar.current.date(byAdding: .year, value: 1, to: Date())
}
}
func makeUIView(context: Context) -> UITextField {
setDatePickerLimits()
datePicker.locale = Locale(identifier: L10n.calendarLocaleIdentifier)
datePicker.datePickerMode = .date
datePicker.preferredDatePickerStyle = .wheels
datePicker.addTarget(self.helper, action: #selector(self.helper.dateValueChanged), for: .valueChanged)
textField.placeholder = placeholder
textField.backgroundColor = Asset.backgroundTextFiledViewColor.color
textField.layer.cornerRadius = 10
textField.inputView = datePicker
let toolbar = UIToolbar()
toolbar.sizeToFit()
let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let doneButton = UIBarButtonItem(title: L10n.titleChoose, style: .plain, target: helper, action: #selector(helper.doneButtonTapped))
let cancelButton = UIBarButtonItem(title: L10n.buttonTitleCancel, style: .plain, target: helper, action: #selector(helper.cancelButtonTapped))
toolbar.setItems([cancelButton, flexibleSpace, doneButton], animated: true)
textField.inputAccessoryView = toolbar
helper.onDateValueChanged = {
setDate()
}
helper.onDoneButtonTapped = {
setDate()
textField.resignFirstResponder()
}
helper.onCancelButtonTapped = {
textField.resignFirstResponder()
}
return textField
}
func updateUIView(_ uiView: UITextField, context: Context) {
uiView.text = date
}
func makeCoordinator() -> Coordinator {
Coordinator()
}
class Helper {
public var onDateValueChanged: (() -> Void)?
public var onDoneButtonTapped: (() -> Void)?
public var onCancelButtonTapped: (() -> Void)?
@objc func dateValueChanged() {
onDateValueChanged?()
}
@objc func doneButtonTapped() {
onDoneButtonTapped?()
}
@objc func cancelButtonTapped() {
onCancelButtonTapped?()
}
}
class Coordinator {}
}
And here is its usage in View:
DatePickerTextField(typeOfDatePicker: .startDate,
placeholder: L10n.textAvailableFrom, date: $createNewOffersViewModel.offersDTO.seasonStart,
dateLimit: $createNewOffersViewModel.offersDTO.seasonEnd)
.frame(height: 50, alignment: .center)
.onChange(of: $createNewOffersViewModel.offersDTO.seasonStart.wrappedValue) { newValue in
if !newValue.isEmpty {
self.createNewOffersViewModel.createOfferValidation[CreateNewOfferValidationEnum.seasonStart] = true
}
}