I wrote a working function for the application, but the error came out "The nil value was unexpectedly found when an optional value was implicitly deployed" limit Limit label.the text I can't fix.
Properties:
@IBOutlet weak var limitLabel: UILabel!
Function:
func leftLabels(){
let limit = self.realm.objects(Limit.self)
guard limit.isEmpty == false else {return}
limitLabel.text = limit[0].limitSum //Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
let calendar = Calendar.current
let formatter = DateFormatter()
formatter.dateFormat = "yyyy/MM/dd HH:mm"
let firstDay = limit[0].limitDate as Date
let lastDay = limit[0].limitLastDate as Date
let firstComponent = calendar.dateComponents([.year, .month, .day], from: firstDay)
let lastComponent = calendar.dateComponents([.year, .month, .day], from: lastDay)
let startDate = formatter.date(from: "\(firstComponent.year!)/\(firstComponent.month!)/\(firstComponent.day!) 00:00")
let endDate = formatter.date(from: "\(lastComponent.year!)/\(lastComponent.month!)/\(lastComponent.day!) 23:59")
let filterLimit: Int = realm.objects(SpendingDB.self).filter("self.date >= %@ && self.date <= %@", startDate ?? "", endDate ?? "").sum(ofProperty: "cost")
ForThePeriod.text = "\(filterLimit)"
let a = Int(limitLabel.text!)!
let b = Int(ForThePeriod.text!)!
let c = a - b
availableForSpending.text = "\(c)"
I will be glad if you tell me the correct code
CodePudding user response:
As from comments if appears that your view is not yet loaded and some of your views are still nil
. Your app crashes because in line limitLabel.text = limit[0].limitSum
the limitLabel
is nil
. It would crash regardless of Realm even by calling limitLabel.text = "Hello world!"
You can always guard data that you need to avoid changes in your code. Simply add
guard let limitLabel = limitLabel else { return nil }
guard let ForThePeriod = ForThePeriod else { return nil }
and so on.
I tried to clean up your code a bit. It is hard to understand what exactly are you trying to achieve but something like the following may seem a bit more appropriate:
func leftLabels() {
// Elements needed for method to execute.
guard let limitLabel = limitLabel else { return }
guard let forThePeriodLabel = forThePeriodLabel else { return }
guard let availableForSpendingLabel = availableForSpendingLabel else { return }
// Items that will be reused throughout the method later on
let limits: [Limit]
let firstLimit: Limit
let dates: (start: Date?, end: Date?)
let filterLimit: Int
limits = self.realm.objects(Limit.self)
guard limits.isEmpty == false else { return }
firstLimit = limits[0]
// limitLabel
limitLabel.text = firstLimit.limitSum
// Date components
dates = {
let calendar = Calendar.current
let formatter = DateFormatter()
formatter.dateFormat = "yyyy/MM/dd HH:mm"
let firstDay = firstLimit.limitDate as Date
let lastDay = firstLimit.limitLastDate as Date
let firstComponent = calendar.dateComponents([.year, .month, .day], from: firstDay)
let lastComponent = calendar.dateComponents([.year, .month, .day], from: lastDay)
let startDate = formatter.date(from: "\(firstComponent.year!)/\(firstComponent.month!)/\(firstComponent.day!) 00:00")
let endDate = formatter.date(from: "\(lastComponent.year!)/\(lastComponent.month!)/\(lastComponent.day!) 23:59")
return (startDate, endDate)
}()
// forThePeriodLabel
filterLimit = realm.objects(SpendingDB.self).filter("self.date >= %@ && self.date <= %@", startDate ?? "", endDate ?? "").sum(ofProperty: "cost")
forThePeriodLabel.text = String(filterLimit)
// availableForSpendingLabel
availableForSpendingLabel.text = {
guard let a = Int(firstLimit.limitSum) else { return "" }
let b = filterLimit
let c = a - b
return String(c)
}()
}
Note some practices which help you better to structure and solve your code.
- Guard dangerous data at first
- Create a list of reusable items for your method (there should be as fewer as possible, in most cases none). Note how these can be later assigned to. And if you try using it before assigning to it, you will be warned by your compiler.
- Wrap as much code into closed sections such as
availableForSpendingLabel.text = { ... code here ... }()
- Use tuples such as
let dates: (start: Date?, end: Date?)
- Don't be afraid of using long names such as
availableForSpendingLabel
I would even further try and break this down into multiple methods. But I am not sure what this method does and assume that you have posted only part of it...
========== EDIT: Adding alternate approach ==========
From comments this is a financial application so probably at least dealing with Decimal
numbers would make sense. Also introducing approach with adding a new structure which resolves data internally. A formatter is also used to format the number. And some other improvements:
struct Limit {
let amount: Decimal
let startDate: Date
let endDate: Date
}
struct Spending {
let cost: Decimal
let date: Date
}
struct LimitReport {
let limitAmount: Decimal
let spendingSum: Decimal
let balance: Decimal
init(limit: Limit) {
let limitAmount: Decimal = limit.amount
let spendingSum: Decimal = {
let calendar = Calendar.autoupdatingCurrent // Is this OK or should it be some UTC or something?
func beginningOfDate(_ date: Date) -> Date {
let components = calendar.dateComponents([.day, .month, .year], from: date)
return calendar.date(from: components)!
}
let startDate = beginningOfDate(limit.startDate)
let endDate = calendar.date(byAdding: .day, value: 1, to: startDate)
let spendings: [Spending] = realm.objects(Spending.self).filter { $0.date >= startDate && $0.date < endDate }
return spendings.reduce(0, { $0 $1.cost })
}()
let balance = limitAmount - spendingSum
self.limitAmount = limitAmount
self.spendingSum = spendingSum
self.balance = balance
}
}
func leftLabels() {
// Elements needed for method to execute.
guard let limitLabel = limitLabel else { return }
guard let forThePeriodLabel = forThePeriodLabel else { return }
guard let availableForSpendingLabel = availableForSpendingLabel else { return }
guard let limit = self.realm.objects(Limit.self).first else { return }
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.currencySymbol = "$"
let report = LimitReport(limit: limit)
limitLabel.text = formatter.string(from: report.limitAmount)
forThePeriodLabel.text = formatter.string(from: report.spendingSum)
availableForSpendingLabel.text = formatter.string(from: report.balance)
}
CodePudding user response:
Matic provided a good, comprehensive answer to your question (voted). I thought I'd provide an answer narrowly focused on your crash and a "short and sweet" way to fix it:
The line in question could crash 2 different ways:
limitLabel.text = limit[0].limitSum //Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
Your limitLabel
IBOutlet is declared as an "implicitly unwrapped Optional" (Note the !
after the type, UILabel
:
@IBOutlet weak var limitLabel: UILabel!
An implicitly unwrapped Optional is an Optional where, essentially, the compiler adds a hidden "!" force-unwrap every time you try to reference that object.
That means that
limitLabel.text = //something
Is compiled as
limitLabel!.text = //something
and if limitLabel
is nil, you crash.
If you call your leftLabels()
function before your view has been loaded, or if that outlet is never connected, you will crash.
You can fix that by adding an optional unwrap to the statement:
limitLabel?.text = //something
(That construct is known as "optional chaining".)
Given that the crash message you're getting mentions "implicitly unwrapping an Optional value" it's likely that that is what is crashing in your case. However, you should fix the other issue as well.
The second way you can crash is in your array indexing.
limitLabel.text = limit[0].limitSum
When you fetch an object from an array by index, your app will crash if the array does not contain an item at that index. The expression limit[0]
will crash if the limit
array is empty.
The array type has a computed property first
that will return an optional if the array is empty.
You should change that to limit.first?.limitSum
.
Change the whole line to be:
limitLabel?.text = limit.first()?.limitSum
And it won't crash any more.