Я создал «Баннер уведомлений» и через 7,5 секунд перейти к следующему уведомлению. С правой стороны у меня есть несколько маркеров, чтобы вы могли видеть, когда находитесь на уведомлении номер 2 из 5.
Чего я хочу добиться, так это медленно заполнять пузырь от серого цвета до белого цвета при работе таймера. Итак, каждый пузырь медленно заполняется, после этого переходите к следующему и т. д. В настоящее время заполняется постепенно, вот так (см. видео ниже).
struct SoleInAppNotifications: View {
@State private var currentIndex: Int = 0
@State private var isPaused: Bool = false
@State var hasImage: Bool? = true
let timer = Timer.publish(every: 7.5, on: .main, in: .common).autoconnect()
var body: some View {
HStack(spacing: .zero) {
if hasImage ?? false {
Image("raiff")
.padding(.trailing, 17)
}
VStack(alignment: .leading, spacing: .zero) {
Text("Notification #1")
.foregroundColor(.white)
.lineLimit(1)
.padding(.bottom, 1)
Text("Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, se")
.foregroundColor(.white)
.lineLimit(2)
}
VStack(spacing: .zero) {
ForEach(0..<5, id: \.self) { index in
BubbleView(isActive: index == currentIndex, counter: currentIndex, countTo: 5)
}
}
}
.frame(maxWidth: .infinity)
.padding(17)
.background(Color.black)
.onReceive(timer) { _ in
if !isPaused, currentIndex < 5 {
withAnimation {
currentIndex = (currentIndex + 1)
}
}
}
}
}
struct BubbleView: View {
let isActive: Bool
var counter: Int
var countTo: Int
var body: some View {
ZStack {
if isActive {
RoundedRectangle(cornerRadius: 3)
.fill(Color.gray)
.frame(width: 6, height: 24)
.overlay(
RoundedRectangle(cornerRadius: 3).trim(from:0, to: progress())
.foregroundColor(
(completed() ? Color.gray : Color.white)
)
).animation(
.easeInOut(duration: 0.2)
)
} else {
Circle()
.fill(Color.gray)
.frame(width: 6, height: 6)
.animation(.easeInOut)
.padding(4)
}
}
}
func completed() -> Bool {
return progress() == 1
}
func progress() -> CGFloat {
return (CGFloat(counter) / CGFloat(countTo))
}
}





Чтобы добиться анимации загрузки, прогресс всегда должен начинаться с 0 и заканчиваться на 1.
В вашей функции прогресса вы устанавливаете прогресс в соответствии с текущим индексом и счетчиком. В результате последний слайд начинается со 100 %, что, насколько я понимаю, не является ожидаемым результатом.
Чтобы добиться полной анимации каждого пузырька, вам нужно будет рассчитать время загрузки на основе статического значения — скорости, которую вы хотите достичь. Например. 7,5 секунд из 100 (измените это значение по своему желанию).
struct SoleInAppNotifications: View {
//...
@State private var progress: CGFloat = 0
let timer = Timer.publish(every: 7.5/100,
on: .main,
in: .common).autoconnect()
//...
var body: some View {
//...
BubbleView(progress: $progress,
isActive: index == currentIndex,
counter: currentIndex,
countTo: 5)
//...
.onReceive(timer) { _ in
withAnimation {
if progress < 1 {
progress += 1/100
} else if !isPaused, currentIndex < 5 {
currentIndex = (currentIndex + 1)
progress = 0
}
}
}
}
}
struct BubbleView: View {
@Binding var progress: CGFloat
//...
/* Remove this function
func progress() -> CGFloat {
return (CGFloat(counter) / CGFloat(countTo))
}*/
}
(хотя и базовая реализация, все же можно использовать некоторые улучшения)
class Bubble {
var counter: CGFloat
var countTo: CGFloat
var i: CGFloat
var isActive: Bool
var progress: CGFloat
init(counter: CGFloat,
countTo: CGFloat,
i: CGFloat,
isActive: Bool,
progress: CGFloat) {
self.counter = counter
self.countTo = countTo
self.i = i
self.isActive = isActive
self.progress = progress
}
}
struct SoleInAppNotifications: View {
@StateObject private var vm = ViewModel()
let animationTimer = Timer.publish(every: 7.5/100,
on: .main,
in: .common).autoconnect()
var body: some View {
HStack(spacing: .zero) {
Image("raiff")
.padding(.trailing, 17)
VStack(alignment: .leading, spacing: .zero) {
Text("Notification #1")
.foregroundColor(.white)
.lineLimit(1)
.padding(.bottom, 1)
Text("Notification body")
.foregroundColor(.white)
.lineLimit(2)
}
VStack(spacing: .zero) {
ForEach(vm.bubbles.indices, id: \.self) { i in
BubbleView(bubble: $vm.bubbles[i])
.onChange(of: vm.currentIndex) { _, _ in
vm.bubbles[i].isActive = CGFloat(i) == vm.currentIndex
}
.onChange(of: vm.progress) { _, _ in
vm.bubbles[i].progress = vm.progress
}
}
}
}
.frame(maxWidth: .infinity)
.padding(17)
.background(Color.black)
.onReceive(animationTimer){ _ in
withAnimation {
if vm.progress < 1 {
vm.progress += 1/100
} else if vm.currentIndex < 5 {
vm.currentIndex += 1
vm.progress = 0
}
}
}
}
}
extension SoleInAppNotifications {
class ViewModel: ObservableObject {
@Published var bubbles: [Bubble]
@Published var currentIndex: CGFloat = 0
@Published var progress: CGFloat = 0
init() {
self.bubbles = []
for i in 0..<5 {
bubbles.append(Bubble(counter: currentIndex,
countTo: 5,
i: CGFloat(i),
isActive: i == 0 ? true : false,
progress: progress))
}
}
}
}
struct BubbleView: View {
@Binding var bubble: Bubble
var body: some View {
if bubble.isActive {
RoundedRectangle(cornerRadius: 3)
.fill(Color.gray)
.frame(width: 6, height: 24)
.overlay(
RoundedRectangle(cornerRadius: 3)
.trim(from:0,
to: bubble.progress)
.foregroundColor(
(completed ? Color.gray : Color.white)
)
).animation(
.easeInOut(duration: 0.2)
)
} else {
Circle()
.fill(Color.gray)
.frame(width: 6, height: 6)
.animation(.easeInOut)
.padding(4)
}
}
var completed: Bool {
bubble.progress == 1
}
}
Пожалуйста, не усложняйте работу другим людям, портя свои посты. Размещая информацию в сети Stack Exchange, вы предоставляете Stack Exchange безотзывное право в соответствии с лицензией CC BY-SA 4.0 на распространение этого контента (т. е. независимо от вашего будущего выбора). Согласно политике Stack Exchange, распространяется не подвергшаяся вандализму версия сообщения. Таким образом, любой вандализм будет устранен. Если вы хотите узнать больше об удалении публикации, прочтите: Как работает удаление?