How can I fix my compass application that makes a complete turn when going 359° from 0°?

Updated: This will now work when rotating multiple times. Thanks to krjw for pointing out the issue.

There's no reason you need your angle property to stay within 360°. Instead of assigning heading to angle directly, calculate the difference and add it.

Here's a working example. Outside your body property in ContentView, add the following functions:

// If you ever need the current value of angle clamped to 0..<360,
//   use clampAngle(self.angle)
func clampAngle(_ angle: CGFloat) -> CGFloat {
    var angle = angle
    while angle < 0 {
        angle += 360
    }
    return angle.truncatingRemainder(dividingBy: 360)
}

// Calculates the difference between heading and angle
func angleDiff(to heading: CGFloat) -> CGFloat {
    return (clampAngle(heading - self.angle) + 180).truncatingRemainder(dividingBy: 360) - 180
}

Then change the line that assigns angle to

self.angle += self.angleDiff(to: heading)

The answer of John M. based on the comments works, however when going into the positive direction, over 360 degrees, the animation fails again.

The simple solution would be to look for a change of -300 and smaller and then add 360 degrees. Theres is probably a better solution, but until then I will share mine:

.onReceive(self.location.heading) { heading in
    var diff = (heading - self.angle + 180).truncatingRemainder(dividingBy: 360) - 180
    if diff < -300 {
        diff += 360
    }
    withAnimation {
        self.angle += diff
    }
}