Next, you’ll add an indicator of a flight’s progress to the card. The status of a flight will usually be either before departure or after landing. In between, there’s a time when the flight will be partway between airports.
Add the following code to the FlightCardView after the flight property:
func minutesBetween(_ start: Date, and end: Date) -> Int {
// 1
let diff = Calendar.current.dateComponents(
[.minute], from: start, to: end
)
// 2
guard let minute = diff.minute else {
return 0
}
// 3
return abs(minute)
}
This method takes two Date objects and returns the minutes between them.
-
The
dateComponents(_:from:to:)method returns the difference between two dates in the requested units — in this case, minutes. -
If something went horribly wrong and the
minuteproperty doesn’t exist, return zero minutes. - Return the absolute value of the number of minutes. The absolute value returns only the magnitude, ignoring the sign, always resulting in a positive value.
You can use this method to get the flight’s progress as Double between zero and one. Add the following new method after minutesBetween(_:and:):
func flightTimeFraction(flight: FlightInformation) -> Double {
// 1
let now = Date()
// 2
if flight.direction == .departure {
// 3
if flight.localTime > now {
return 0.0
// 4
} else if flight.otherEndTime < now {
return 1.0
} else {
// 5
let timeInFlight = minutesBetween(
flight.localTime, and: now
)
// 6
let fraction =
Double(timeInFlight) / Double(flight.flightTime)
return fraction
}
} else {
if flight.otherEndTime > now {
return 0.0
} else if flight.localTime < now {
return 1.0
} else {
let timeInFlight = minutesBetween(
flight.otherEndTime, and: now
)
let fraction =
Double(timeInFlight) / Double(flight.flightTime)
return fraction
}
}
}
There’s a lot here, and it’s somewhat repetitive, but that’s necessary to handle all the possible cases:
-
You put the current
Dateinto a variable, which you’ll refer to often in this method. - The first case covers departing flights. The case for arriving flights works the same but with the times swapped.
-
If the
localTimefor the departing flight is after now, the flight has not departed yet, meaning the fraction is zero. -
If the
otherEndTimeparameter for the departing flight is before now, the flight already arrived, meaning the fraction is one. -
If neither is true, the flight is in the air. This code uses the
minutesBetween(_:and:)method to get the minutes between now and the flight’s departure time in minutes. -
The
flightTimeparameter stores the flight’s total length. You calculate the fraction as the value calculated in the last step, divided by the flight’s length.
With a method to calculate the flight’s location, you’ll add a graphical representation in the next section.
Adding Inline Drawings
Now, you’ll add a view to show the flight’s progress. Create a new SwiftUI view named FlightProgressView inside the Timeline group. Change the view to:
struct FlightProgressView: View {
var flight: FlightInformation
var progress: CGFloat
var body: some View {
// 1
GeometryReader { proxy in
Image(systemName: "airplane")
.resizable()
// 2
.offset(x: proxy.size.width * progress)
.frame(width: 25, height: 25)
.foregroundColor(flight.statusColor)
// 3
}.padding([.trailing], 20)
}
}
#Preview {
FlightProgressView(
flight: FlightData.generateTestFlight(date: Date()),
progress: 0.67
)
}
This code uses a GeometryReader to allow the fine positioning of an image.
-
The
GeometryReadercauses the view to fill the space. It also provides aGeometryProxy, which contains the view’s width, among other information. -
You take the view’s width from the
sizeproperty on theGeometryProxy. Multiplying this value by the fraction of the flight gives an offset to reflect the flight’s progress as a portion of the view’s total width. - The offset affects the leading edge of the image, meaning the image continues past that point. When the fraction nears 1.0, the image spills into the next view. You add a 20-point padding to the view’s trailing edge, providing a space for the image.
To use the new view, go to FlightCardView and replace the Spacer between DepartureTimeView and ArrivalTimeView with:
FlightProgressView(
flight: flight,
progress: flightTimeFraction(
flight: flight
)
)
Build and run the app, and you’ll see the progress indicator added to each flight.
Now that you have the underlying view in place, you can improve the view’s appearance. Open FlightCardView.swift and add the following at the end of the VStack:
.padding()
.background(
Color.gray.opacity(0.3)
)
.clipShape(
RoundedRectangle(cornerRadius: 20)
)
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke()
)