最终目标是创建无限滚动视图。我面临着一些我不知道如何解决的挑战。我向您展示我现在拥有的代码,它与最终的样子相去甚远:
var body: some View {
ScrollView(.horizontal) {
LazyHStack {
ForEach(datesToShow, id: \.self) { value in
VStack {
Text("\(value.day)")
Text(value.toString(.custom("MMMM")).capitalized)
.padding(.top)
}
.foregroundStyle(.white)
.frame(width: 100, height: 100)
.background(.purple)
}
}
}
.frame(height: 200)
.onAppear {
}
}
datesToShow
是一个日期数组,包含今天 - 10 天到今天 + 10 天之间的日期。现在,艰难的时刻从这里开始:
datesToShow
数组中以便自动加载(当然性能最好)?谢谢!
按照我使用轮播的建议,这里有一个示例实现来展示它的工作原理。
日期在
ZStack
中显示为单独的视图,每个视图都有一个计算出的 x 偏移量。 ZStack
上的拖动手势可以更改当前日期。释放拖动后,它会捕捉到最近的日期。
在这个例子中,有 15 个底层视图,它们不断循环。我发现最好的办法是拥有比实际可见数量多得多的视图,这样当快速拖动时,您就不会看到更新过程中的日期。让数字取决于显示器的宽度,或者至少使每个视图的大小取决于宽度,这样它也可以在大显示器上工作,这可能是一个好主意。
希望有帮助!
struct DateView: View {
let baseDate: Date
let dayAdjustment: Int
let dayOfMonthFormatter = DateFormatter()
let monthNameFormatter = DateFormatter()
init(baseDate: Date, dayAdjustment: Int) {
self.baseDate = baseDate
self.dayAdjustment = dayAdjustment
dayOfMonthFormatter.dateFormat = "d"
monthNameFormatter.dateFormat = "MMMM"
}
var body: some View {
let dateToDisplay = Calendar.current.date(byAdding: .day, value: dayAdjustment, to: baseDate) ?? baseDate
ZStack {
Color.purple
.cornerRadius(10)
VStack {
Text(dayOfMonthFormatter.string(from: dateToDisplay))
Text(monthNameFormatter.string(from: dateToDisplay))
}
.foregroundStyle(.white)
}
}
}
struct ContentView: View {
let nPanels = 15
let dateItemSize: CGFloat = 100
let gapSize: CGFloat = 10
let baseDate = Date.now
@State private var dayOffset = Double.zero
@State private var dragBegin = 0
private var positionWidth: CGFloat {
CGFloat(dateItemSize + gapSize)
}
private func relativePositionForIndex(index: Int) -> Double {
let midIndex = Double(nPanels / 2)
var dIndex = (Double(index) - dayOffset - midIndex).truncatingRemainder(dividingBy: Double(nPanels))
if dIndex < -midIndex {
dIndex += Double(nPanels)
} else if dIndex > midIndex {
dIndex -= Double(nPanels)
}
return dIndex
}
private func dateView(index: Int, halfFullWidth: CGFloat) -> some View {
let dIndex = relativePositionForIndex(index: index)
let xOffset = dIndex * positionWidth
let dayAdjustment = Int(dIndex.rounded()) + Int(dayOffset.rounded())
return DateView(
baseDate: baseDate,
dayAdjustment: dayAdjustment
)
.frame(width: dateItemSize, height: dateItemSize)
.offset(x: xOffset)
// Setting opacity helps to avoid blinks when switching sides
.opacity(xOffset + positionWidth < -halfFullWidth || xOffset - positionWidth > halfFullWidth ? 0 : 1)
}
private var dragged: some Gesture {
DragGesture()
.onChanged() { val in
dayOffset = Double(dragBegin) - (val.translation.width / positionWidth)
}
.onEnded { val in
dragBegin = Int(Double(dragBegin) - (val.translation.width / positionWidth).rounded())
withAnimation(.easeInOut(duration: 0.15)) {
dayOffset = Double(dragBegin)
}
}
}
var body: some View {
GeometryReader { proxy in
let halfFullWidth = proxy.size.width / 2
ZStack {
ForEach(0..<nPanels, id: \.self) { index in
dateView(index: index, halfFullWidth: halfFullWidth)
}
RoundedRectangle(cornerRadius: 10)
.stroke(lineWidth: 3)
.opacity(0.7)
.frame(width: dateItemSize + gapSize, height: dateItemSize + gapSize)
}
.gesture(dragged)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
}