前言
前面对小组件动画有了基本了解,最后讲到 clockHandRotationEffect 私有API的限制问题,这两天发现有大佬对 clockHandRotationEffect 私有API限制研究出了新的对策,可以采用 XCFramework 形式导入使用,根据这一思路尝试实现一个动态小组件效果。末尾附上完整示例。
引入Framework
注意:
有大佬提供了编译好的 Framework 我们可以选择直接使用,也可以自己编译自己的 ClockHandRotation Framework。
项目中需要设置 Framework 为 embed**,**否则会提示找不到
ClockHandRotationKit 已放到文末。我们可以使用 Package 引用也可以下载到本地。
dependencies: [
.package(url: "https://github.com/octree/ClockHandRotationKit", from: "1.0.0")
]
我这里选择本地引用
处理gif展示
小组件是不支持加载 gif 图的,这里需要对 gif 进行处理,可以参考 DynamicWidget 的实现思路。
func getGif(_ gifName: String) -> UIImage.GifResult? {
guard gifName.count > 0 else { return nil }
guard let gifPath = Bundle.main.path(forResource: gifName, ofType: "gif") else {
return nil
}
guard FileManager.default.fileExists(atPath: gifPath),
let gifData = try? Data(contentsOf: URL(fileURLWithPath: gifPath))
else {
return nil
}
return UIImage.decodeGIF(gifData)
}
封装一个展示 gif 的控件,提供 gif 名称即可识别加载
import SwiftUI
import ClockHandRotationKit
...
struct GifImageView: View {
var gifName: String // Bundle中 gif图片的名称
var defaultImage: String // 默认图片
var body: some View {
if let gif = getGif(gifName) {
GeometryReader { proxy in
let width = proxy.size.width
let height = proxy.size.height
let arcWidth = max(width, height)
let arcRadius = arcWidth * arcWidth
let angle = 360.0 / Double(gif.images.count)
ZStack {
ForEach(1...gif.images.count, id: \.self) { index in
Image(uiImage: gif.images[(gif.images.count - 1) - (index - 1)])
.resizable()
.aspectRatio(contentMode: .fill)
.frame(minWidth: 0, minHeight: 0)
.mask(
ArcView(arcStartAngle: angle * Double(index - 1),
arcEndAngle: angle * Double(index),
arcRadius: arcRadius)
.stroke(style: .init(lineWidth: arcWidth * 1.1, lineCap: .square, lineJoin: .miter))
.frame(width: width, height: height)
.clockHandRotationEffect(period: .custom(gif.duration))
.offset(y: arcRadius) // ⚠️ 需要先进行旋转,再设置offset
)
}
}
.frame(width: width, height: height)
}
} else {
Image(systemName: defaultImage)
.resizable()
.aspectRatio(contentMode: .fill)
}
}
}
struct ArcView: Shape {
var arcStartAngle: Double
var arcEndAngle: Double
var arcRadius: Double
func path(in rect: CGRect) -> Path {
var path = Path()
path.addArc(center: CGPoint(x: rect.midX, y: rect.midY),
radius: arcRadius,
startAngle: .degrees(arcStartAngle),
endAngle: .degrees(arcEndAngle),
clockwise: false)
return path
}
}
小组件UI
小组件中主要是UI布局展示,修改为自己想要的样式,我这里参考了 OneDay 项目的样式风格。
import SwiftUI
import WidgetKit
struct GifAnimateWidgetEntryView : View {
var entry: CommonProvider.Entry
var body: some View {
VStack(alignment: .leading) {
VStack(alignment: .leading, spacing: 0) {
Text(dateInfo.day)
.font(.custom("DINAlternate-Bold", size: 28))
.foregroundColor(.white)
+ Text(" / \(dateInfo.month)")
.font(.custom("DINAlternate-Bold", size: 14))
.foregroundColor(.white)
Text("\(dateInfo.year), \(dateInfo.weekday)")
.font(.custom("PingFangSC", size: 10))
.foregroundColor(.white.opacity(0.9))
}
Spacer()
Text("不与伪君子争名,不与真小人争利")
.font(.custom("PingFangSC", size: 14))
.foregroundColor(.white)
.lineSpacing(4)
.multilineTextAlignment(.leading)
.frame(maxWidth: .infinity, alignment: .leading)
}
.baseShadow()
.padding(.horizontal, 14)
.padding(.top, 13)
.padding(.bottom, 18)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.background(
Color.black.opacity(0.15)
)
.background(
GifImageView(gifName: "transformer", defaultImage: "")
)
}
}
struct GifAnimateWidget: Widget {
let kind: String = "GifAnimateWidget"
var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: CommonProvider()) { entry in
GifAnimateWidgetEntryView(entry: entry)
}
.configurationDisplayName("Gif动画小组件")
.description("This is an example widget.")
.supportedFamilies([.systemSmall])
.adoptableWidgetContentMargin()
}
}
#Preview(as: .systemSmall) {
GifAnimateWidget()
} timeline: {
CommonEntry(date: .now)
}
效果
项目链接
代码量有点多,我放到了github上,需要的自取
github.com/MisterZhouZ…
参考资料
本文同步自微信公众号 "程序员小溪" ,这里只是同步,想看及时消息请移步我的公众号,不定时更新我的学习经验。