Swiftui动画日历日期
Swiftui animation calendar dates
在我的应用程序中,我想通过从一个日期到下一个日期、下一个日期、下一个日期等的“日历”过渡来显示时间的流逝。因此,例如,如果我想显示日期从 18 日过渡到 19 日,再到 20 日,我将显示 18 1 秒,然后淡出,淡入 19,淡出,然后淡入 20。
我有以下内容来显示一个日期到下一个日期的动画(例如 18 > 19th):
struct Calendar: View {
@State var date: String
var body: some View {
VStack {
Spacer()
ZStack {
RoundedRectangle(cornerRadius: 20)
.stroke(Color.black, lineWidth: 2)
.frame(width: 200, height: 200)
RoundedRectangle(cornerRadius: 20)
.fill(Color.red)
.frame(width: 200, height: 200)
.offset(y: 160)
.clipped()
.offset(y: -160)
RoundedRectangle(cornerRadius: 20)
.stroke(Color.black, lineWidth: 2)
.frame(width: 200, height: 200)
.offset(y: 160)
.clipped()
.offset(y: -160)
Text(date).font(.system(size: 70.0))
.offset(y: 20)
}
Spacer()
Spacer()
}.padding()
}
}
我在我的代码中使用:
ScrollView(showsIndicators: false) {
VStack {
Spacer()
ZStack {
if showseconddate == false {
Calendar(date: "18").animation(.easeInOut(duration: 1.0))
.transition(.opacity)
}
if showseconddate == true {
Calendar(date: "19").animation(.easeInOut(duration: 1.0))
.transition(.opacity)
}
Spacer()
}
}.onAppear {
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { timer in
withAnimation(Animation.linear(duration: 0.5)) {
self.showseconddate.toggle()
self.showfirstdate.toggle() }
timer.invalidate()
}
}
}
这一切都按预期工作,但我正在努力将其扩展到我想展示它通过多个日期转换的情况,例如 18 > 19 >20 >21 等。有谁知道如何扩展这个,还是使用替代解决方案?任何解决方案都必须淡出旧日期,然后淡入新日期。非常感谢!
这是一个相对紧凑的解决方案。它不依赖于 Bool
值,而是循环遍历数组:
struct ContentView: View {
private var dates = ["18","19","20","21","22"]
@State private var dateIndex = 0
private let timer = Timer.publish(every: 1.0, on: .main, in: .common).autoconnect()
var body: some View{
ScrollView(showsIndicators: false) {
VStack {
Spacer()
ZStack {
Calendar(date: dates[dateIndex])
.transition(.opacity)
.id("date-\(dateIndex)")
Spacer()
}
}.onReceive(timer) { _ in
var newIndex = dateIndex + 1
if newIndex == dates.count { newIndex = 0 }
withAnimation(.easeInOut(duration: 0.5)) {
dateIndex = newIndex
}
}
}
}
}
我重写了你的代码来获得动画 运行,我觉得看着整个日历闪烁有点烦人,所以我把它重写成一个 CalendarPage
(我将日历重命名为CalendarPage,因为 Calendar 是 Swift) 和 CalendarView
中的一种类型,它采用日期并将其覆盖在页面上。
CalendarPage 是删除了 date
var 和 Text()
的日历:
struct CalendarPage: View {
var body: some View {
VStack {
Spacer()
ZStack {
RoundedRectangle(cornerRadius: 20)
.stroke(Color.black, lineWidth: 2)
.frame(width: 200, height: 200)
RoundedRectangle(cornerRadius: 20)
.fill(Color.red)
.frame(width: 200, height: 200)
.offset(y: 160)
.clipped()
.offset(y: -160)
RoundedRectangle(cornerRadius: 20)
.stroke(Color.black, lineWidth: 2)
.frame(width: 200, height: 200)
.offset(y: 160)
.clipped()
.offset(y: -160)
}
Spacer()
Spacer()
}.padding()
}
}
CalendarView 使用计时器递增您的日期,直到到达 endDate
,并且它只影响日期本身的不透明度,而不影响整个日历:
struct CalendarView: View {
@State var date: Int = 0
@State var animate = false
@State var calendarSize: CGFloat = 20
let endDate = 31
// This keeps the font size consistent regardless of the size of the calendar
var fontSize: CGFloat {
calendarSize * 0.45
}
private let timer = Timer.publish(every: 1.0, on: .main, in: .common).autoconnect()
var body: some View {
CalendarPage(date: date.description)
.overlay(alignment: .bottom) {
VStack {
Text(date.description)
.font(.system(size: fontSize))
.opacity(animate ? 1 : 0)
}
.frame(height: calendarSize * 0.8)
}
.frame(width: 200, height: 200)
.readSize(onChange: { size in
calendarSize = min(size.width, size.height)
})
.onReceive(timer) { _ in
date += 1
withAnimation(.linear(duration: 0.3)) {
animate = true
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.75) {
if date != endDate {
withAnimation(.linear(duration: 0.2)) {
animate = false
}
} else {
timer.upstream.connect().cancel()
}
}
}
}
}
我还使用首选项键来计算 CalendarPage
的高度(尽管我可以硬编码)使用 FiveStars blog
的 View
扩展
extension View {
func readSize(onChange: @escaping (CGSize) -> Void) -> some View {
background(
GeometryReader { geometryProxy in
Color.clear
.preference(key: SizePreferenceKey.self, value: geometryProxy.size)
}
)
.onPreferenceChange(SizePreferenceKey.self, perform: onChange)
}
}
fileprivate struct SizePreferenceKey: PreferenceKey {
static var defaultValue: CGSize = .zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {}
}
在我的应用程序中,我想通过从一个日期到下一个日期、下一个日期、下一个日期等的“日历”过渡来显示时间的流逝。因此,例如,如果我想显示日期从 18 日过渡到 19 日,再到 20 日,我将显示 18 1 秒,然后淡出,淡入 19,淡出,然后淡入 20。
我有以下内容来显示一个日期到下一个日期的动画(例如 18 > 19th):
struct Calendar: View {
@State var date: String
var body: some View {
VStack {
Spacer()
ZStack {
RoundedRectangle(cornerRadius: 20)
.stroke(Color.black, lineWidth: 2)
.frame(width: 200, height: 200)
RoundedRectangle(cornerRadius: 20)
.fill(Color.red)
.frame(width: 200, height: 200)
.offset(y: 160)
.clipped()
.offset(y: -160)
RoundedRectangle(cornerRadius: 20)
.stroke(Color.black, lineWidth: 2)
.frame(width: 200, height: 200)
.offset(y: 160)
.clipped()
.offset(y: -160)
Text(date).font(.system(size: 70.0))
.offset(y: 20)
}
Spacer()
Spacer()
}.padding()
}
}
我在我的代码中使用:
ScrollView(showsIndicators: false) {
VStack {
Spacer()
ZStack {
if showseconddate == false {
Calendar(date: "18").animation(.easeInOut(duration: 1.0))
.transition(.opacity)
}
if showseconddate == true {
Calendar(date: "19").animation(.easeInOut(duration: 1.0))
.transition(.opacity)
}
Spacer()
}
}.onAppear {
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { timer in
withAnimation(Animation.linear(duration: 0.5)) {
self.showseconddate.toggle()
self.showfirstdate.toggle() }
timer.invalidate()
}
}
}
这一切都按预期工作,但我正在努力将其扩展到我想展示它通过多个日期转换的情况,例如 18 > 19 >20 >21 等。有谁知道如何扩展这个,还是使用替代解决方案?任何解决方案都必须淡出旧日期,然后淡入新日期。非常感谢!
这是一个相对紧凑的解决方案。它不依赖于 Bool
值,而是循环遍历数组:
struct ContentView: View {
private var dates = ["18","19","20","21","22"]
@State private var dateIndex = 0
private let timer = Timer.publish(every: 1.0, on: .main, in: .common).autoconnect()
var body: some View{
ScrollView(showsIndicators: false) {
VStack {
Spacer()
ZStack {
Calendar(date: dates[dateIndex])
.transition(.opacity)
.id("date-\(dateIndex)")
Spacer()
}
}.onReceive(timer) { _ in
var newIndex = dateIndex + 1
if newIndex == dates.count { newIndex = 0 }
withAnimation(.easeInOut(duration: 0.5)) {
dateIndex = newIndex
}
}
}
}
}
我重写了你的代码来获得动画 运行,我觉得看着整个日历闪烁有点烦人,所以我把它重写成一个 CalendarPage
(我将日历重命名为CalendarPage,因为 Calendar 是 Swift) 和 CalendarView
中的一种类型,它采用日期并将其覆盖在页面上。
CalendarPage 是删除了 date
var 和 Text()
的日历:
struct CalendarPage: View {
var body: some View {
VStack {
Spacer()
ZStack {
RoundedRectangle(cornerRadius: 20)
.stroke(Color.black, lineWidth: 2)
.frame(width: 200, height: 200)
RoundedRectangle(cornerRadius: 20)
.fill(Color.red)
.frame(width: 200, height: 200)
.offset(y: 160)
.clipped()
.offset(y: -160)
RoundedRectangle(cornerRadius: 20)
.stroke(Color.black, lineWidth: 2)
.frame(width: 200, height: 200)
.offset(y: 160)
.clipped()
.offset(y: -160)
}
Spacer()
Spacer()
}.padding()
}
}
CalendarView 使用计时器递增您的日期,直到到达 endDate
,并且它只影响日期本身的不透明度,而不影响整个日历:
struct CalendarView: View {
@State var date: Int = 0
@State var animate = false
@State var calendarSize: CGFloat = 20
let endDate = 31
// This keeps the font size consistent regardless of the size of the calendar
var fontSize: CGFloat {
calendarSize * 0.45
}
private let timer = Timer.publish(every: 1.0, on: .main, in: .common).autoconnect()
var body: some View {
CalendarPage(date: date.description)
.overlay(alignment: .bottom) {
VStack {
Text(date.description)
.font(.system(size: fontSize))
.opacity(animate ? 1 : 0)
}
.frame(height: calendarSize * 0.8)
}
.frame(width: 200, height: 200)
.readSize(onChange: { size in
calendarSize = min(size.width, size.height)
})
.onReceive(timer) { _ in
date += 1
withAnimation(.linear(duration: 0.3)) {
animate = true
}
DispatchQueue.main.asyncAfter(deadline: .now() + 0.75) {
if date != endDate {
withAnimation(.linear(duration: 0.2)) {
animate = false
}
} else {
timer.upstream.connect().cancel()
}
}
}
}
}
我还使用首选项键来计算 CalendarPage
的高度(尽管我可以硬编码)使用 FiveStars blog
View
扩展
extension View {
func readSize(onChange: @escaping (CGSize) -> Void) -> some View {
background(
GeometryReader { geometryProxy in
Color.clear
.preference(key: SizePreferenceKey.self, value: geometryProxy.size)
}
)
.onPreferenceChange(SizePreferenceKey.self, perform: onChange)
}
}
fileprivate struct SizePreferenceKey: PreferenceKey {
static var defaultValue: CGSize = .zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {}
}