如何使用 SwiftUI 为一系列图像制作动画?
How to animate a sequence of images using SwiftUI?
如何使用 SwiftUI 框架为一系列图像(比如第 3 帧 1.png 到第 6 帧)设置动画?
我试过创建 "images" 的数组。然后我将 UIImage.animatedImage(with: images, duration: 1.0) 方法分配给一个名为 "animatedImage" 的变量
最后我在 "ContentView.swift"
的 "body" 中尝试了 Image(uiImage: animatedImage)
var images: [UIImage]! = [UIImage(named: "Sequence/frame-1")!,
UIImage(named: "Sequence/frame-2")!,
UIImage(named: "Sequence/frame-3")!,
UIImage(named: "Sequence/frame-4")!,
UIImage(named: "Sequence/frame-5")!,
UIImage(named: "Sequence/frame-6")!
]
let animatedImage : UIImage! = UIImage.animatedImage(with: images, duration: 1.0)
//////Then in the ContentView.swift I've tried this code:
struct ContentView : View {
var body: some View {
Image(uiImage: animatedImage)
}
}
当我 运行 程序时它只显示第一帧,但我期望帧的动画
您尝试过制作自己的视图包装器吗?
像这样:
struct AnimatedImage: UIViewRepresentable {
func makeUIView(context: Self.Context) -> UIImageView {
return UIImageView(image: animatedImage)
}
func updateUIView(_ uiView: UIImageView, context: UIViewRepresentableContext<AnimatedImage>) {
}
}
用法为:
struct ContentView : View {
var body: some View {
AnimatedImage()
}
}
不确定这是否能解决您的问题,至少它不再是静态图像了:D
在 Xcode Beta 2 模拟器上试过了。
接受的答案非常有效,bhagyash ingale 提到的不幸问题使其很难使用。如果 Image
的特定方法可以通过协议或其他方式重用,那将会很有用。我有一个非常糟糕的,也许是巨大的大炮来解决这个问题,也许及时会更容易,但现在......
class LoadingTimer {
let publisher = Timer.publish(every: 0.1, on: .main, in: .default)
private var timerCancellable: Cancellable?
func start() {
self.timerCancellable = publisher.connect()
}
func cancel() {
self.timerCancellable?.cancel()
}
}
struct LoadingView: View {
@State private var index = 0
private let images = (0...7).map { UIImage(named: "Image-\([=11=]).jpg")! }
private var timer = LoadingTimer()
var body: some View {
return Image(uiImage: images[index])
.resizable()
.frame(width: 100, height: 100, alignment: .center)
.onReceive(
timer.publisher,
perform: { _ in
self.index = self.index + 1
if self.index >= 7 { self.index = 0 }
}
)
.onAppear { self.timer.start() }
.onDisappear { self.timer.cancel() }
}
}
我不喜欢这个,但它完成了工作并依赖于 Image
。
没有 Combine 的版本
import SwiftUI
struct AnimatingImage: View {
let images: [Image]
@ObservedObject private var counter = Counter(interval: 0.05)
var body: some View {
images[counter.value % images.count]
}
}
private class Counter: ObservableObject {
private var timer: Timer?
@Published var value: Int = 0
init(interval: Double) {
timer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { _ in self.value += 1 }
}
}
可以用
struct LoadingView: View {
private let images = (1...20).map { String(format: "i_%02d", [=11=]) }.map { Image([=11=]) }
var body: some View {
AnimatingImage(images: images)
}
}
我想按顺序重复命名三个图像资产:thinking1、thinking2 和 thinking3。根据 nightwill 的回答,这适用于 iOS 14.4
// images[counter.value % images.count] mod ensures counter never exceeds images.count
import SwiftUI
struct ThinkingView: View {
let images: [Image] = (1...3).map { String(format: "thinking%01d", [=10=]) }.map { Image([=10=]) }
@ObservedObject private var counter = Counter(interval: 0.1)
var body: some View {
images[counter.value % images.count]
}
}
private class Counter: ObservableObject {
private var timer: Timer?
@Published var value: Int = 0
init(interval: Double) {
timer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { _ in self.value += 1 }
}
}
struct ThinkingView_Previews: PreviewProvider {
static var previews: some View {
ThinkingView()
}
}
我从图像数组中找出了一个纯 SwftUI 动画:
它使用动画修改器来处理要显示的图像而不是视图(这里是带有图像的按钮)。当你点击一个按钮时,它的图像就会动画化。如果您再次点击图像停止动画和默认按钮图像
显示:
//
// ImageArrayAnimationView
//
// Created by Pitt Xav
//
import SwiftUI
struct ImageArrayAnimationView: View {
// List of images
private let images: [Image] = [
Image(systemName: "icloud.and.arrow.up"),
Image(systemName: "icloud.and.arrow.down"),
Image(systemName: "cloud"),
Image(systemName: "cloud.drizzle"),
Image(systemName: "cloud.rain"),
Image(systemName: "cloud.heavyrain")
]
// Images and percent variable use for animating rain down
private let imagesOrderDown = [2, 4, 5]
@State var percentDown: Float = 0
// Images and percent variable use for animating rain up
@State var percentUp: Float = 0
private let imagesOrderUp = [5, 3, 2]
var body: some View {
VStack {
Spacer()
HStack {
Spacer()
// tapping a button set percent to :
// 0 = no animation (Animation.default)
// 100 = animation with images
Button {percentDown = percentDown < 50 ? 100 : 0} label: {
Image(systemName: "icloud.and.arrow.down")
.resizable()
.frame(width: 150, height: 150, alignment: .top)
.imageArrayAnimation(images: images, imagesOrder: imagesOrderDown, percent: percentDown)
.animation(percentDown <= 0 ? Animation.default : .linear(duration: 1).repeatForever(autoreverses: false), value: percentDown)
.border(.foreground, width: 2)
Spacer()
}
Button {percentUp = percentUp < 50 ? 100 : 0} label: {
Image(systemName: "icloud.and.arrow.up")
.resizable()
.frame(width: 150, height: 150, alignment: .top)
.imageArrayAnimation(images: images, imagesOrder: imagesOrderUp, percent: percentUp)
.animation(percentUp <= 0 ? Animation.default : .linear(duration: 1).repeatForever(autoreverses: false), value: percentUp)
.border(.foreground, width: 2)
Spacer()
}
}
.frame(width: 100, height: 100, alignment: .center)
Spacer()
}
}
}
struct ImageAnimation: AnimatableModifier {
// Animation through th percent property
// percent is converted into a position in an index array
// this index value enable to choose whch image in the array
// to display
var animatableData: Float {
get {
percent
}
set {
percent = newValue
}
}
var images: [Image]
var imgesOrder: [Int]
var percent: Float
var currentImageIndex: Int {
if percent > 0 {
let index = Int((percent / 100) * Float(imgesOrder.count))
print("% \(percent) \(index)")
return imgesOrder[min(index, imgesOrder.count-1)]
} else {
return 0
}
}
func body(content: Content) -> some View {
ZStack(alignment: .top) {
// for the animation to work content and image
// needs to always be there
// opacity enable to choose what to actually display
content
.opacity(percent == 0 ? 1 : 0)
images[currentImageIndex]
.resizable()
.scaledToFit()
.opacity(percent == 0 ? 0 : 1)
}
}
}
// extension to have mnamed modifier
extension View {
func imageArrayAnimation(images: [Image], imagesOrder: [Int], percent: Float) -> some View {
self.modifier(ImageAnimation(images: images, imgesOrder: imagesOrder, percent: percent))
}
}
struct ImageArrayAnimationView_Previews: PreviewProvider {
static var previews: some View {
ImageArrayAnimationView()
}
}
如何使用 SwiftUI 框架为一系列图像(比如第 3 帧 1.png 到第 6 帧)设置动画?
我试过创建 "images" 的数组。然后我将 UIImage.animatedImage(with: images, duration: 1.0) 方法分配给一个名为 "animatedImage" 的变量 最后我在 "ContentView.swift"
的 "body" 中尝试了 Image(uiImage: animatedImage)
var images: [UIImage]! = [UIImage(named: "Sequence/frame-1")!,
UIImage(named: "Sequence/frame-2")!,
UIImage(named: "Sequence/frame-3")!,
UIImage(named: "Sequence/frame-4")!,
UIImage(named: "Sequence/frame-5")!,
UIImage(named: "Sequence/frame-6")!
]
let animatedImage : UIImage! = UIImage.animatedImage(with: images, duration: 1.0)
//////Then in the ContentView.swift I've tried this code:
struct ContentView : View {
var body: some View {
Image(uiImage: animatedImage)
}
}
当我 运行 程序时它只显示第一帧,但我期望帧的动画
您尝试过制作自己的视图包装器吗?
像这样:
struct AnimatedImage: UIViewRepresentable {
func makeUIView(context: Self.Context) -> UIImageView {
return UIImageView(image: animatedImage)
}
func updateUIView(_ uiView: UIImageView, context: UIViewRepresentableContext<AnimatedImage>) {
}
}
用法为:
struct ContentView : View {
var body: some View {
AnimatedImage()
}
}
不确定这是否能解决您的问题,至少它不再是静态图像了:D
在 Xcode Beta 2 模拟器上试过了。
接受的答案非常有效,bhagyash ingale 提到的不幸问题使其很难使用。如果 Image
的特定方法可以通过协议或其他方式重用,那将会很有用。我有一个非常糟糕的,也许是巨大的大炮来解决这个问题,也许及时会更容易,但现在......
class LoadingTimer {
let publisher = Timer.publish(every: 0.1, on: .main, in: .default)
private var timerCancellable: Cancellable?
func start() {
self.timerCancellable = publisher.connect()
}
func cancel() {
self.timerCancellable?.cancel()
}
}
struct LoadingView: View {
@State private var index = 0
private let images = (0...7).map { UIImage(named: "Image-\([=11=]).jpg")! }
private var timer = LoadingTimer()
var body: some View {
return Image(uiImage: images[index])
.resizable()
.frame(width: 100, height: 100, alignment: .center)
.onReceive(
timer.publisher,
perform: { _ in
self.index = self.index + 1
if self.index >= 7 { self.index = 0 }
}
)
.onAppear { self.timer.start() }
.onDisappear { self.timer.cancel() }
}
}
我不喜欢这个,但它完成了工作并依赖于 Image
。
没有 Combine 的版本
import SwiftUI
struct AnimatingImage: View {
let images: [Image]
@ObservedObject private var counter = Counter(interval: 0.05)
var body: some View {
images[counter.value % images.count]
}
}
private class Counter: ObservableObject {
private var timer: Timer?
@Published var value: Int = 0
init(interval: Double) {
timer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { _ in self.value += 1 }
}
}
可以用
struct LoadingView: View {
private let images = (1...20).map { String(format: "i_%02d", [=11=]) }.map { Image([=11=]) }
var body: some View {
AnimatingImage(images: images)
}
}
我想按顺序重复命名三个图像资产:thinking1、thinking2 和 thinking3。根据 nightwill 的回答,这适用于 iOS 14.4
// images[counter.value % images.count] mod ensures counter never exceeds images.count
import SwiftUI
struct ThinkingView: View {
let images: [Image] = (1...3).map { String(format: "thinking%01d", [=10=]) }.map { Image([=10=]) }
@ObservedObject private var counter = Counter(interval: 0.1)
var body: some View {
images[counter.value % images.count]
}
}
private class Counter: ObservableObject {
private var timer: Timer?
@Published var value: Int = 0
init(interval: Double) {
timer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { _ in self.value += 1 }
}
}
struct ThinkingView_Previews: PreviewProvider {
static var previews: some View {
ThinkingView()
}
}
我从图像数组中找出了一个纯 SwftUI 动画: 它使用动画修改器来处理要显示的图像而不是视图(这里是带有图像的按钮)。当你点击一个按钮时,它的图像就会动画化。如果您再次点击图像停止动画和默认按钮图像 显示:
//
// ImageArrayAnimationView
//
// Created by Pitt Xav
//
import SwiftUI
struct ImageArrayAnimationView: View {
// List of images
private let images: [Image] = [
Image(systemName: "icloud.and.arrow.up"),
Image(systemName: "icloud.and.arrow.down"),
Image(systemName: "cloud"),
Image(systemName: "cloud.drizzle"),
Image(systemName: "cloud.rain"),
Image(systemName: "cloud.heavyrain")
]
// Images and percent variable use for animating rain down
private let imagesOrderDown = [2, 4, 5]
@State var percentDown: Float = 0
// Images and percent variable use for animating rain up
@State var percentUp: Float = 0
private let imagesOrderUp = [5, 3, 2]
var body: some View {
VStack {
Spacer()
HStack {
Spacer()
// tapping a button set percent to :
// 0 = no animation (Animation.default)
// 100 = animation with images
Button {percentDown = percentDown < 50 ? 100 : 0} label: {
Image(systemName: "icloud.and.arrow.down")
.resizable()
.frame(width: 150, height: 150, alignment: .top)
.imageArrayAnimation(images: images, imagesOrder: imagesOrderDown, percent: percentDown)
.animation(percentDown <= 0 ? Animation.default : .linear(duration: 1).repeatForever(autoreverses: false), value: percentDown)
.border(.foreground, width: 2)
Spacer()
}
Button {percentUp = percentUp < 50 ? 100 : 0} label: {
Image(systemName: "icloud.and.arrow.up")
.resizable()
.frame(width: 150, height: 150, alignment: .top)
.imageArrayAnimation(images: images, imagesOrder: imagesOrderUp, percent: percentUp)
.animation(percentUp <= 0 ? Animation.default : .linear(duration: 1).repeatForever(autoreverses: false), value: percentUp)
.border(.foreground, width: 2)
Spacer()
}
}
.frame(width: 100, height: 100, alignment: .center)
Spacer()
}
}
}
struct ImageAnimation: AnimatableModifier {
// Animation through th percent property
// percent is converted into a position in an index array
// this index value enable to choose whch image in the array
// to display
var animatableData: Float {
get {
percent
}
set {
percent = newValue
}
}
var images: [Image]
var imgesOrder: [Int]
var percent: Float
var currentImageIndex: Int {
if percent > 0 {
let index = Int((percent / 100) * Float(imgesOrder.count))
print("% \(percent) \(index)")
return imgesOrder[min(index, imgesOrder.count-1)]
} else {
return 0
}
}
func body(content: Content) -> some View {
ZStack(alignment: .top) {
// for the animation to work content and image
// needs to always be there
// opacity enable to choose what to actually display
content
.opacity(percent == 0 ? 1 : 0)
images[currentImageIndex]
.resizable()
.scaledToFit()
.opacity(percent == 0 ? 0 : 1)
}
}
}
// extension to have mnamed modifier
extension View {
func imageArrayAnimation(images: [Image], imagesOrder: [Int], percent: Float) -> some View {
self.modifier(ImageAnimation(images: images, imgesOrder: imagesOrder, percent: percent))
}
}
struct ImageArrayAnimationView_Previews: PreviewProvider {
static var previews: some View {
ImageArrayAnimationView()
}
}