LAST CHANCE: Save 50% on all my Swift books and bundles! >>

SOLVED: Day 47: Challenge: error when updating Habit counter value

Forums > 100 Days of SwiftUI

This app seemed simple at first, but working with data is challenging my understanding more than I expected. I have the app is working other than the Habit counter increase/update: It's goving me an error I can't seem to decode. What am I doing wrong?

This is my HabitDetailView

import SwiftUI

struct HabitDetailView: View {
    @ObservedObject var listOfHabits = Habits()
    let habit: Habit

    var body: some View {
        ZStack{
            VStack {
                //EXTEND icon
                Text(habit.name)
                Text(habit.description)
                Text("\(habit.counter)")
                HStack {
                    Button {
                        var tempHabit = habit
                        tempHabit.counter += 1
                        var index = listOfHabits.items.firstIndex(of: habit)
                        listOfHabits.items[index] = tempHabit // ERROR IS HERE, marked by [index]
                    } label: {
                        Image(systemName: "plus")
                    }
                    HStack {
                        Button {
                            // decrease count
                        } label: {
                            Image(systemName: "minus")
                        }
                    }
                }
            }
        }
    }
}

#Preview {
    HabitDetailView(habit: Habits.sampleHabit)
}

and these are my struct and class:

import Foundation

struct Habit: Codable, Identifiable, Equatable {
    var id = UUID()
    var name: String
    var description: String
    var counter: Int
}
import Foundation

class Habits: ObservableObject {
    @Published var items = [Habit](){
        didSet {
            let encoder = JSONEncoder()

            if let encoded = try? encoder.encode(items){
                UserDefaults.standard.set(encoded, forKey: "Habits")
            }
        }
    }

    init() {
        if let savedHabits = UserDefaults.standard.data(forKey: "Habits") {
            if let decodedHabits = try? JSONDecoder().decode([Habit].self, from: savedHabits){
                items = decodedHabits
                return
            }
        }
        items = []
    }

    static let sampleHabit = Habit(name: "Testhabit", description: "This is a test description, and it's long so it will end up on two lines.", counter: 6)
}

and of course, here's my error 🤨

Value of optional type 'Array<Habit>.Index?' (aka 'Optional<Int>') must be unwrapped to a value of type 'Array<Habit>.Index' (aka 'Int')

Coalesce Using '??' to provide a default when the optional value contains nil

Force-unwrap using '!' to abort execution if the optional value contains 'nil

Of course, using ?? and ! crashed the app. I would rather find the real mistake and learn to understand the error and stick in a quick fix anyway.

3      

Hi! Try to use if let to make sure there is an item with such index.

if let index = listOfHabits.items.firstIndex(of: habit) {
  listOfHabits.items[index] = tempHabit  // index is not optional any more
}

3      

Thank you @ygeras. Please tell me if I understand this correctly:

  • this is because in this particular view, listOfHabits is not defined, so this view cannot tell if there is indeed an item in the list
  • so it should be handled as an optional

When I am unclear about it, when the program is running, to get to this specific View one would need to have at least one item, the specifics of which would be pushed to this view when HabitDetailView is activated. So it would never be emtpy, because if the array of Habits was empty, then one could not click on a item and get details... so why handle it as an optional? Because HabitDetailView doesn't know all of this?

3      

The answer to your question is much simpler. You need to know how method of firstIndex(of: ) works on arrays. The result is always returned as Optional. Why? Imagine the situation if value MUST be returned, and there is no such value in the Array, what do you expect to happen? Crash!

It's you, who knows that there is particular value available in the array but compiler does not, so to be save it returns Optional.

As an example you could see what happens if we force unwrap the value, kind of situation if the method returned non-optional.

let array = ["One", "Two", "Three"]

let indexOfOne = array.firstIndex(of: "Two")
print(indexOfOne!) // prints 1

let indexOfFive = array.firstIndex(of: "Five")
print(indexOfFive!) // crashes!

4      

@ygeras ah yes, that makes complete sense. Thank you so much!

3      

Hacking with Swift is sponsored by Essential Developer.

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until July 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.