Is there an ActivityIndicator in WatchKit for Apple Watch?
Asked Answered
S

9

37

Is there an ActivityIndicator (or something like it) in WatchKit for Apple Watch? How do you all give the user feedback about some longer lasting background activity?

Scurlock answered 6/3, 2015 at 18:20 Comment(4)
Honestly, after reading Apple's documentation and guidelines, if you need an activity indicator or progress bar then whatever you're trying to do is not suitable for the Apple Watch. Apple explicitly says that developers can "expect interactions with watch apps to be in seconds [not minutes]". Interactions are quick. A user shouldn't have to wait more than a second for something to load.Mapes
That sounds good in theory, but when fetching data over the network, you can´t guarantee that it´s all ready in 1 second. The WatchKit interface does nothing when I´m loading my JSON with Alamofire. The user only sees a black watch screen and thinks the app is broken. What would you do to fix this?Scurlock
Check my answer for more details on this. Hopefully in a future iteration of WATCH, you'll be able to perform these tasks right on the device.Mapes
You can also do something like this but as many people suggested better not do it. youtube.com/watch?feature=player_embedded&v=_8YVt7V1mAAColumbine
M
17

Edit: This answer was originally posted prior to the introduction of Apple Watch models with cellular and wifi connectivity, and thus may no longer apply on newer models of the device (considering significant performance improvements).


This thread on the Apple Developer forums has an authoritative answer from an Apple engineer about why you shouldn't be performing network operations with Apple Watch.

There are two big reasons not to perform networking operations from your watch app / extension:

  1. Users are interacting with their watches for only a brief period of time. See the Human Interface guidelines on this one.

    If you measure interactions with your iOS app in minutes, you can expect interactions with your WatchKit app to be measured in seconds. So interactions should be brief and interfaces should be simple.

  2. The system may deadlock if the network request does not complete.

    Our recommendation is that, in general, you should not perform complex networking operations inside a WatchKit Extension...

    [Apple recommends that developers] have a single process that is in charge of updating the information in your database (likely your iOS app), and then your extensions would have (essentially) read-only access to this [cached] database....


That being said. If you really need a UIActivityIndicator, rdar://19363748 (I don't think this one has been open radar-ed yet), developers have already filed requests for official support.

You can create a series of images in the activity indicator style of your choice and then animate them using the startAnimatingWithImagesInRange:duration:repeatCount: API. See Apple's Lister app for an example of animation.

Alternatively, look here for a WatchKit Animation tutorial and included "spinner" graphics.

Mapes answered 8/3, 2015 at 20:35 Comment(4)
+1, because 2) is completely new to me. Well, I read the thread and they´re talking about CloudKit. The Apple engineer also throws in the keyword 'complex networking'. I don´t think JSON via HTTP is complex networking. But anyways, I see the point and have to let that sink in.Scurlock
What the apple-guy says is that you should perform any network request by launching your main app in background, i.e. not doing the actual networking code in you extension app. This is because the watchkit extension (may) only live when the app is open on the watch. With that said, it doesn't mean that a watchkit app can not initiate a request that happens async somewhere else - a spinner is one way to show that something is going on, if the developer wants to illustrate it this way.Maeve
There is an activity indicator on the watch. My app does async background network ops from the companion app and, if the network is slow, it may take a while. It doesn't matter where the net activity is happening, it would still be nice to be able to show the user that it's going on. There's already an indicator on the watch, so it would be a waste of precious (limited) resources to have to download a sequence of images to the watch for something that already exists in its on-board system, and it would also present a more consistent experience for the user .Sidonius
Thank you for spinner gif linkWeeper
S
22

Just to add to the options, I've created a JBWatchActivityIndicator project on GitHub that lets you generate your own image sequences: https://github.com/mikeswanson/JBWatchActivityIndicator

It also includes Apple-like activity indicator animations if you don't want to create your own.

Swansea answered 8/5, 2015 at 22:30 Comment(0)
M
17

Edit: This answer was originally posted prior to the introduction of Apple Watch models with cellular and wifi connectivity, and thus may no longer apply on newer models of the device (considering significant performance improvements).


This thread on the Apple Developer forums has an authoritative answer from an Apple engineer about why you shouldn't be performing network operations with Apple Watch.

There are two big reasons not to perform networking operations from your watch app / extension:

  1. Users are interacting with their watches for only a brief period of time. See the Human Interface guidelines on this one.

    If you measure interactions with your iOS app in minutes, you can expect interactions with your WatchKit app to be measured in seconds. So interactions should be brief and interfaces should be simple.

  2. The system may deadlock if the network request does not complete.

    Our recommendation is that, in general, you should not perform complex networking operations inside a WatchKit Extension...

    [Apple recommends that developers] have a single process that is in charge of updating the information in your database (likely your iOS app), and then your extensions would have (essentially) read-only access to this [cached] database....


That being said. If you really need a UIActivityIndicator, rdar://19363748 (I don't think this one has been open radar-ed yet), developers have already filed requests for official support.

You can create a series of images in the activity indicator style of your choice and then animate them using the startAnimatingWithImagesInRange:duration:repeatCount: API. See Apple's Lister app for an example of animation.

Alternatively, look here for a WatchKit Animation tutorial and included "spinner" graphics.

Mapes answered 8/3, 2015 at 20:35 Comment(4)
+1, because 2) is completely new to me. Well, I read the thread and they´re talking about CloudKit. The Apple engineer also throws in the keyword 'complex networking'. I don´t think JSON via HTTP is complex networking. But anyways, I see the point and have to let that sink in.Scurlock
What the apple-guy says is that you should perform any network request by launching your main app in background, i.e. not doing the actual networking code in you extension app. This is because the watchkit extension (may) only live when the app is open on the watch. With that said, it doesn't mean that a watchkit app can not initiate a request that happens async somewhere else - a spinner is one way to show that something is going on, if the developer wants to illustrate it this way.Maeve
There is an activity indicator on the watch. My app does async background network ops from the companion app and, if the network is slow, it may take a while. It doesn't matter where the net activity is happening, it would still be nice to be able to show the user that it's going on. There's already an indicator on the watch, so it would be a waste of precious (limited) resources to have to download a sequence of images to the watch for something that already exists in its on-board system, and it would also present a more consistent experience for the user .Sidonius
Thank you for spinner gif linkWeeper
I
5

I built a simple activity indicator for the Apple Watch, available here https://github.com/tijoinc/WatchActivityIndicator

Industrial answered 25/9, 2015 at 14:39 Comment(0)
M
5

SwiftUI has a built-in solution:

ProgressView()

It'll look like this:

Marlborough answered 15/3, 2021 at 10:47 Comment(0)
I
4

There is no method for displaying ActivityIndicator in WatchKit Framework. However you can prepare some circular image and easily create infinite animation yourself. Prepare images and name them like this frame-0, frame-1, frame-2...frame-n

and then in your code:

    [self.yourInterfaceImage setImageNamed:@"firstFrame-"]; //setting first frame
    [self.yourInterfaceImage startAnimatingWithImagesInRange:[self.model imageRange]
                                               duration:0.4
                                            repeatCount:0];
    // [self.model imageRange] will return NSRange from 0 to n
    // repeatCount == 0 means infinity. Of course you can set some limit, like 100.

Hope this helps.

Irritating answered 7/3, 2015 at 14:11 Comment(3)
Basically, I´m searching for the standard circulating ActivityIndicator that WatchKit uses when starting up a new ViewController. If someone can deliver exactly this as a Copy+Paste code, he will get my accepted answer. :)Scurlock
Take a look at my answer, that´s exactly what I needed. +1 for having half of the answer.Scurlock
But if i mention the same name like firstFrame- in the setImageNamed. It gives me the error saying unable to find the image name. Is there any configuration change required for this??Jyoti
M
4

I made it similar to the watchOS indicator with swiftUI.

enter image description here

import SwiftUI

struct ActivityIndicatorView: View {

    // MARK: - Value
    // MARK: Public
    @Binding var isAnimating: Bool


    // MARK: Private
    private let radius: CGFloat = 24.0
    private let count = 18
    private let interval: TimeInterval = 0.1

    private let point = { (index: Int, count: Int, radius: CGFloat, frame: CGRect) -> CGPoint in
        let angle   = 2.0 * .pi / Double(count) * Double(index)
        let circleX = radius * cos(CGFloat(angle))
        let circleY = radius * sin(CGFloat(angle))

       return CGPoint(x: circleX + frame.midX, y: circleY + frame.midY)
   }

   private let timer = Timer.publish(every: 1.8, on: .main, in: .common).autoconnect()     // every(1.8) = count(18) / interval(0.1)

   @State private var scale: CGFloat  = 0
   @State private var opacity: Double = 0

   // MARK: - View
   var body: some View {
       GeometryReader { geometry in
            ForEach(0..<self.count) { index in
                Circle()
                    .fill(Color.white)
                    .frame(width: 3.0, height: 3.0)
                    .animation(nil)
                    .opacity(self.opacity)
                    .scaleEffect(self.scale)
                    .position(self.point(index, self.count, self.radius, geometry.frame(in: .local)))
                    .animation(
                        Animation.easeOut(duration: 1.0)
                            .repeatCount(1, autoreverses: true)
                            .delay(TimeInterval(index) * self.interval)
                     )
             }
             .onReceive(self.timer) { output in
                self.update()
             }
        }
        .rotationEffect(.degrees(10.0))
        .opacity(isAnimating == false ? 0 : 1.0)
        .onAppear {
            self.update()
        }
    }



    // MARK: - Function
    // MARK: Private
    private func update() {
        scale   = 0 < scale ? 0 : 1.0
        opacity = 0 < opacity ? 0 : 1.0

        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
            self.scale   = 0
            self.opacity = 0
        }
    }
}

#if DEBUG
struct ActivityIndicatorView_Previews: PreviewProvider {

    static var previews: some View {
        let view = ActivityIndicatorView(isAnimating: .constant(true))

        return Group {
             view
                 .previewDevice("Apple Watch Series 5 - 44mm")

             view
                 .previewDevice("Apple Watch Series 4 - 40mm")

             view
                 .previewDevice("Apple Watch Series 3 - 42mm")

             view
                 .previewDevice("Apple Watch Series 3 - 38mm")
         }
     }
 }
 #endif
Millesimal answered 11/6, 2020 at 10:38 Comment(0)
T
2

In my opinion, trying to create your own Spinner is using excessive resources. If Apple thought it was a good idea, they would have suggested it.

I would instead just have an Image that you adjust the Alpha. Use a boolean to see if you should be adding or subtracting Alpha.

if (add)
    {
        count=count+5;
        if (count==100)
        {
            add=false;
        }
    }
    else
    {
        count=count-5;
        if (count==0)
        {
            add=true;
        }
    }

    float thealpha=((float)count/100);
    [self.scanb setAlpha:thealpha];

}

Tubby answered 28/8, 2015 at 15:10 Comment(0)
E
1

Here is an simple text indicator, which uses a @State attribute:

struct MyView: View {
    private let loaderSpeed = 0.1 // seconds per state
    private let loaderStates = [
        "•       ",
        " •      ",
        "  •     ",
        "   •    ",
        "    •   ",
        "     •  ",
        "      • ",
        "       •",
        "      • ",
        "     •  ",
        "    •   ",
        "   •    ",
        "  •     ",
        " •      ",
    ]
    @State private var loaderMessage = ""
    @State private var loaderState = 0 {
        didSet {
            if self.loaderState > 0 {
                DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + self.loaderSpeed) {
                    if self.loaderState > 0 {
                        self.loaderMessage = self.loaderStates[self.loaderState-1]
                        if self.loaderState >= self.loaderStates.count {
                             self.loaderState = 1
                        } else {
                            self.loaderState += 1
                        }
                    }
                }

            }
        }
    }

    var body: some View {
        HStack() {
            Spacer()
            Text("Loading:")
            Text(loaderMessage).onAppear { self.loaderState = 1 }
            Spacer()
        }
    }
}

set loaderState = 1 to start the loader

set loaderState = 0 to stop the loader

Expectant answered 7/2, 2020 at 19:55 Comment(0)
B
0

use default one ProgressView() supported on watchos https://developer.apple.com/documentation/SwiftUI/ProgressView

Bell answered 11/3 at 12:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.