Search code examples
iosswiftxcodeswift3uikit

Unwrapping an Optional value in swift and realm


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

enter image description here


Solution

  • 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)
    }