Я пытаюсь создать собственный индикатор выполнения следующим образом (пока игнорируйте большой палец):
На данный момент я могу постепенно раскрыть волну, используя еще один прямоугольник, примененный в качестве маски. Я не уверен, как обрезать левую часть строки по мере увеличения прогресса. Я попытался установить представление волны с маской в качестве маски для линии, но это не сработало. Должен ли я обновлять ширину линии в зависимости от прогресса или можно использовать маску таким образом, чтобы, когда волна начинает становиться видимой от переднего края, линия сжималась по направлению к заднему краю? Посоветуйте, пожалуйста, эффективный способ это сделать. Любая помощь приветствуется.
struct Line: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint(x: 0, y: rect.height/2))
path.addLine(to: CGPoint(x: rect.width, y: rect.height/2))
return path
}
}
struct Wave: Shape {
var strength: Double
var frequency: Double
var phase: Double
var animatableData: Double {
get { phase }
set { self.phase = newValue }
}
func path(in rect: CGRect) -> Path {
var path = Path()
let width = Double(rect.width)
let height = Double(rect.height)
let midHeight = height / 2
let wavelength = width / frequency
let firstX = 0.0
let firstRelativeX = firstX / wavelength
let firstSine = sin(firstRelativeX + phase)
let firstY = strength * firstSine + midHeight
path.move(to: CGPoint(x: firstX, y: firstY))
for x in stride(from: 0.0, through: width, by: 1) {
let relativeX = x / wavelength
let sine = sin(relativeX + phase)
let y = strength * sine + midHeight
path.addLine(to: CGPoint(x: x, y: y))
}
return path
}
}
struct ContentView: View {
@State private var phase = 0.0
@State private var progress = 0.0
var body: some View {
VStack {
GeometryReader(content: { geometry in
ZStack {
Line()
.stroke(Color.white.opacity(0.5), lineWidth: 5.0)
.frame(width: geometry.size.width)
Wave(strength: 10, frequency: 30, phase: phase)
.stroke(Color.white, lineWidth: 5)
.mask(
Rectangle()
.frame(width: (geometry.size.width * progress), height: geometry.size.height)
.padding(.trailing, geometry.size.width * (1 - progress))
)
}
})
Slider(value: $progress, in: 0...1)
.padding()
}
.background(Color.black.ignoresSafeArea())
.onAppear {
withAnimation(Animation.linear(duration: 2).repeatForever(autoreverses: false)) {
self.phase = .pi * 2
}
}
}
}
Да, волна анимирована, и это нормально.
Shape
жадный и занимает столько места, сколько доступно. Таким образом, ваш код можно упростить, применяя фиксированную ширину только там, где это действительно необходимо. Тогда вам не нужно исправлять ширину линии, вам просто нужно добавить отступы.
Кроме того, если вы предоставите параметр alignment
для .mask
, то можно избежать заполнения маски.
GeometryReader { geometry in
let progressWidth = geometry.size.width * progress
ZStack {
Line()
.stroke(Color.white.opacity(0.5), lineWidth: 5.0)
.padding(.leading, progressWidth)
Wave(strength: 10, frequency: 30, phase: phase)
.stroke(Color.white, lineWidth: 5)
.mask(alignment: .leading) {
Rectangle()
.frame(width: progressWidth)
}
}
}
Спасибо! Я как-то пропустил параметр выравнивания модификатора маски.
Разве линия не будет подниматься и опускаться по мере раскрытия волны? Или это нормально, что волна и линия не связаны?