Live Activity – the problems (almost) no one speaks about

So you think you know Live Activities huh? You have a great project in mind and you want to get in as soon as possible? Here we will focus on some issues and I will try to show problems you can encounter during playing with the ActivityKit.

1. Dynamic Island Timer too large ?

The most common usage of live activities is showing time reaming or sport live scores. Apple provides a type for text to display remaining timer or timer but there is problem with it when displayed on Dynamic Island.

Text(.distantPast, style: .timer)
Swift

This code will display a text with style timer but on Dynamic Island it’s going to stretch all the way on the side that it was set at


In my opinion this is probably not a “bug”. We have so little control over the Dynamic Island view mostly, probably (*sic) because Apple decided not to dynamically calculate needed space (due to a variety of reasons). But – I am not going to leave you without a solution.

To improve our design we can use a modifier .monospacedDigit() and a little bit of calculations. This modifier makes all digits have the same width in points. I have made some calculations and we can safely assume a single digit is 10pt and a colon is 4pt. So before setting the view we can count the time reaming and set the width of the timer to occupy the correct space.

func calculateTimeRemainingWidth(range: ClosedRange<Date>) -> CGFloat {
    
    let distance = range.lowerBound.distance(to: range.upperBound)
    let formatter = DateComponentsFormatter()
    formatter.allowedUnits = [.hour, .minute, .second]
    
    guard let formattedDistance = formatter.string(from: distance) else { return 58.0 }
    
    let numberWidth = 10
    let colonWidth = 4
    let components = formattedDistance.components(separatedBy: ":")
    let colonCount = formattedDistance.filter {$0 == ":" }.count
    var totalWidth = 0
    
    for component in components {
        let componentLength = component.count
        totalWidth += componentLength * numberWidth
    }
    
    totalWidth += colonWidth * colonCount
    
    return CGFloat (totalWidth)
}
Swift

Keep in mind that the calculation is made only once! So, for instance, if you calculated the width for example for 1:20:45 (a “bigger” text) and then the time changes to 59:45 (a “smaller” text) the label is not going to adjust on its own. And since the calculation is done only once and you cannot just “force update” the dynamic island view we can only work with the “bigger” text. So we calculate for the “bigger” text and use .multilineTextAlignment(.center) to keep it always on the middle.

Timer view with calculated width.
Timer that width was first calculated for a 1:00:60.

Not great, not terrible!

So keep in mind the values you might have, calculate for the worst case scenario and… profit?

2. Timer is going up

When you are setting a timer, for example, for 15 minutes and it is counting down – after it reaches 00:00 it will start going up!

To fix this “feature” we need to use a Text with a different initializer:

Text(
  timerInterval: ClosedRange<Date>, 
  pauseTime: Date? = nil, 
  countsDown: Bool = true, 
  showsHours: Bool = true
)
Swift

When we provide a ClosedRange<Date> and then the timer will stop when it reaches 00:00!

Not great, not terrible!

3. Time zones are important

As you work with timers you’ll come across another challenge: creating accurate Date ranges. If your users rely on the timer you’ve placed on the Dynamic Island or the Lock Screen the timing must be precise.

I want to emphasize that using TIME ZONES correctly in your dates is crucial and may SAVE LIVES!

If you have a savvy user, they might tinker with time zones, which could disrupt the timer.

4. Debugging the LockScreen Widget

Testing and finding bugs in the LockScreen widget can be a bit tricky. I ran into an issue where the Live Activity started and there was an animation suggesting that the app was transitioning to the Dynamic Island but nothing was actually happening!

My widget included a QR Code image that depended on the server and could have a different size. I was using an Image(named “Test”) without any frame modifiers. It turned out that the image was too large to be displayed on the Lock Screen. Image size can also be problematic for various device sizes. For example, it rendered perfectly on an iPhone 14 Pro, but on an iPhone 11, the Lock Screen widget didn’t appear at all.

To debug this kind of issue using the Xcode console isn’t the way to go. You’ll need to rely on NSLog and the Mac Console app. These tools can provide you with more information about why the Lock Screen widget couldn’t be rendered.

At this image you can see at Console App is giving as a reason why Widget can not be created.

5. Displaying Dynamic Images on Lock screen widget and on Dynamic Island

Imagine you are getting an image from your backend and you want to display that on Dynamic Island or on Lock Screen Widget. In order to do that you need to have to turn on App Groups.

Apple, during developer sessions, admitted that this is the way to go. Mostly because the images you might be using are probably downloaded by the main application and thus are not, by default, accessible by the widget.

Remember the widget is a complementary app! And has its own life cycle of displaying images that are dynamically downloaded from our own servers.

To do that we need to add an App Group and allow that in our entailment panel for our Application on Apple Developer website. Then we are downloading the image to our Main App storage and retrieve it from there.

Conclusion

Live Activity is a great feature that can surly enrich your application. I hope in this post you will find some useful information.


Szymon Szysz is a ex-Apple hater and has come a long way. Now making apps for iOS since 2017. Likes good design and amazing UX. Likes to focus on code quality and does not hesitate to say that something “is not yes” (*ponglish, “is not correct”).



Posted

in

by