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?
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:
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.
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 wkinterfaceimage animation.
Alternatively, look here for a WatchKit Animation tutorial and included "spinner" graphics.
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.
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];
}
I made it similar to the watchOS indicator with swiftUI.
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
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
I built a simple activity indicator for the Apple Watch, available here https://github.com/tijoinc/WatchActivityIndicator