WatchKit中是否有适用于Apple Watch的ActivityIndicator?

37

Apple Watch的WatchKit中是否有ActivityIndicator(或类似的东西)?您如何向用户提供有关某些持续较长的后台活动的反馈?


3
阅读了苹果的文档和指南后,老实说,如果你需要一个活动指示器或进度条,那么你想要做的事情可能不适合在Apple Watch上完成。苹果明确表示,开发者可以“预期与手表应用的交互时间在数秒内[而非数分钟]”。交互是快速的。用户不应该等待超过一秒钟来加载某些内容。 - Sam Spencer
9
理论上听起来不错,但在从网络获取数据时,无法保证所有数据都能在1秒内准备好。当我使用Alamofire加载JSON数据时,WatchKit界面什么也不显示。用户只看到一个黑色的手表屏幕,认为应用程序已经崩溃了。你会怎么做来解决这个问题? - stk
请查看我的答案以获取更多详细信息。希望在未来的WATCH版本中,您将能够直接在设备上执行这些任务。 - Sam Spencer
你也可以像这样做,但是正如许多人建议的那样最好不要这样做。https://www.youtube.com/watch?feature=player_embedded&v=_8YVt7V1mAA - Jay Mayu
8个回答

22

仅作为补充,我在GitHub上创建了一个名为JBWatchActivityIndicator的项目,它可以让您生成自己的图像序列:https://github.com/mikeswanson/JBWatchActivityIndicator

如果您不想创建自己的图像序列,它还包括苹果风格的活动指示器动画。


17

编辑:这篇答案最初发布于Apple Watch没有蜂窝数据和wifi连接的型号问世之前,因此在新款设备上可能不再适用(考虑到重大的性能改进)。


这个苹果开发者论坛的帖子有一位苹果工程师关于为什么你不应该在 Apple Watch 上执行网络操作的权威回答。

有两个重要原因不能从您的手表应用程序/扩展程序执行网络操作:

  1. 用户只与其手表交互了很短的时间。请参见人机界面指南

    如果您将 iOS 应用与用户的互动衡量为几分钟,则可以预计 WatchKit 应用与用户的互动将被衡量为几秒钟。因此,交互应该简短,界面应该简单。

  2. 如果网络请求未完成,则系统可能会死锁。

    我们的建议是,通常情况下,您不应在 WatchKit Extension 内执行复杂的网络操作......

    [苹果建议开发者]有一个负责更新数据库中信息(可能是您的 iOS 应用)的单个进程,然后您的扩展程序将具有(基本上)只读访问权限以访问这个[缓存的]数据库...


话虽如此。如果你真的需要 UIActivityIndicator,rdar:// 19363748(我不认为这个已经被官方支持了),开发人员已经提交了请求。

您可以创建一系列符合您选择的活动指示器样式的图像,然后使用 startAnimatingWithImagesInRange:duration:repeatCount: API 对它们进行动画处理。请参考 Apple 的 Lister 应用程序以获取 动画的实例。
或者,您可以在 这里 查看 WatchKit 动画教程和包含“spinner”图形的 示例文件

+1,因为对于第二点我完全不了解。好吧,我读了这个帖子,他们在谈论CloudKit。苹果工程师还提到了“复杂网络”关键字。我不认为通过HTTP传输JSON是复杂的网络。但无论如何,我理解了他们的观点并需要思考一下。 - stk
4
苹果开发者的意思是,你应该通过在后台启动主应用程序来执行任何网络请求,而不是在扩展应用程序中执行实际的网络编码。这是因为WatchKit扩展可能只在手表上打开应用程序时才能运行。话虽如此,并不意味着WatchKit应用程序不能发起异步请求 - 如果开发人员希望以此方式说明某些操作正在进行中,则可以使用spinner(旋转图标)等方式显示。 - jake_hetfield
1
手表上确实有一个活动指示器。我的应用程序从附属应用程序执行异步后台网络操作,如果网络较慢,则可能需要一些时间。无论网络活动发生在何处,都可以向用户显示正在进行的活动,这样很好。手表上已经有了指示器,因此为了已经存在于其板载系统中的东西而必须下载一系列图像到手表是对宝贵(有限)资源的浪费,并且还可以为用户提供更一致的体验。 - Bill Weinman
感谢您提供旋转动画GIF的链接。 - Almas Adilbek

5

SwiftUI内置了一种解决方案:

ProgressView()

它会像这样:


5

4

在WatchKit Framework中没有显示ActivityIndicator的方法。然而,您可以准备一些圆形图像并轻松地自己创建无限动画。

准备图片并将它们命名为:

frame-0、frame-1、frame-2......frame-n

然后在代码中使用:

    [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.

希望这能帮到你。

基本上,我正在寻找WatchKit在启动新的ViewController时使用的标准循环ActivityIndicator。如果有人能够精确地提供此代码作为复制+粘贴,他将得到我的采纳答案。 :) - stk
看看我的答案,那正是我所需要的。+1 因为你提供了一半的答案。 - stk
但是如果我在setImageNamed中提及相同的名称,例如firstFrame-。它会给我一个错误,说找不到图像名称。这需要进行任何配置更改吗? - Hussain Shabbir

4

我使用swiftUI制作出类似于watchOS指示器的效果。

输入图像描述

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

2

我认为尝试创建自己的Spinner会消耗过多的资源。如果苹果公司认为这是个好主意,他们会建议的。

相反,我会只使用一个Image并调整它的Alpha值。使用一个布尔变量来判断是否应该添加或减少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];

}


1
这是一个简单的文本指示器,使用@State属性:
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()
        }
    }
}

loaderState = 1设置为启动加载器

loaderState = 0设置为停止加载器


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接