Solved Day 46, but with some doubts, posting my solution here and looking to see if I can get some answers/improvement suggestions. In particular, I have a doubt about the last part.
This is what I implemented so far based on the challenge questions
-
Change project 7 (iExpense) so that it uses NavigationLink for adding new expenses rather than a sheet. (Tip: The dismiss() code works great here, but you might want to add the navigationBarBackButtonHidden() modifier so they have to explicitly choose Cancel.)
-
Try changing project 7 so that it lets users edit their issue name in the navigation title rather than a separate textfield. Which option do you prefer?
struct ExpensesChallengeView: View {
@State private var expenses = Expenses()
var body: some View {
VStack {
NavigationStack {
List {
// Display expenses code, code removed for easier reading
}
.navigationTitle("iExpense")
.toolbar {
NavigationLink(destination: AddViewChalllengeView(expenses: expenses)) {
Image(systemName: "plus")
}
}
}
}
}
AddView
struct AddViewChalllengeView: View {
@State private var name = "New Expense"
@State private var type = "Personal"
@State private var amount = 0.0
@Environment(\.dismiss) var dismiss
var expenses: Expenses
let types = ["Business", "Personal"]
var body: some View {
NavigationStack {
Form {
Picker("Type", selection: $type) {
ForEach(types, id: \.self) {
Text($0)
}
}
TextField("Amount", value: $amount, format: .currency(code: Locale.current.currency?.identifier ?? "USD"))
.keyboardType(.decimalPad)
}
.navigationTitle($name)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button("Save") {
let item = ExpenseItem(name: name, type: type, amount: amount)
expenses.items.append(item)
dismiss()
}
}
ToolbarItem(placement: .cancellationAction) {
Button("Cancel") {
dismiss()
}
}
}
}
.navigationBarBackButtonHidden()
}
}
Return to project 8 (Moonshot), and upgrade it to use NavigationLink(value:). This means adding Hashable conformance, and thinking carefully how to use navigationDestination().
This is the part i'm confused, I used NavigationLink(value:) in my grid view and had to conform Mission to Hashable, but in doing that, my Mission Struct had a 'does not conform to equatable' error and thus had to make the Mission struct conform to equatable by implementing a custom == function. Why is this the case? This happens after I change MoonshotGridView (shown below) to use NavigationLink(value:) and I just can't see where it's performing an equality comparison.
struct MoonshotGridView: View {
let astronauts: [String: Astronaut]
let missions: [Mission]
let columns = [GridItem(.adaptive(minimum: 150))]
var body: some View {
ScrollView {
LazyVGrid(columns: columns) {
ForEach(missions) { mission in
NavigationLink(value: mission) {
VStack {
Image(mission.image)
.resizable()
.scaledToFit()
.frame(width: 100, height : 100)
.padding()
VStack {
Text(mission.displayName)
.font(.headline)
.foregroundStyle(.white)
Text(mission.formattedDate)
.font(.caption)
.foregroundStyle(.gray)
}
.padding(.vertical)
.frame(maxWidth: .infinity)
.background(.lightBackground)
}
.clipShape(.rect(cornerRadius: 10))
.overlay(RoundedRectangle(cornerRadius: 10)
.stroke(.lightBackground)
)
}
.navigationDestination(for: Mission.self) { mission in
MissionView(mission: mission, astronauts: astronauts)
}
}
}
.padding([.horizontal, .bottom])
.background(.darkBackground)
}
}
}
import Foundation
struct Mission: Hashable, Codable, Identifiable, Equatable {
static func == (lhs: Mission, rhs: Mission) -> Bool {
return lhs.id == rhs.id
&& lhs.launchDate == rhs.launchDate
&& lhs.crew == rhs.crew
&& lhs.description == rhs.description
}
struct CrewRole: Codable, Equatable, Hashable {
let name: String
let role: String
}
let id: Int
let launchDate: Date?
let crew: [CrewRole]
let description: String
var displayName: String {
"Apollo \(id)"
}
var image: String {
"apollo\(id)"
}
var formattedDate: String {
launchDate?.formatted(date: .abbreviated, time: .omitted) ?? "N/A"
}
}