SwiftUI - xóa mờ ScrollView
Tôi có một biểu đồ quá khổ, được tạo, tôi đưa vào ScrollView để người dùng có thể cuộn sang phải và xem tất cả các giá trị. Tôi muốn cho người dùng biết rằng có "nhiều thứ hơn sẽ đến" ở bên phải bằng cách làm mờ ScrollView ra ngoài. Một cái gì đó trong Swift thật dễ dàng bằng cách áp dụng CAGradientLayer.
Cách tiếp cận của tôi là áp dụng một lớp phủ có độ dốc từ rõ ràng (bắt đầu từ 80%) sang màu nền của hệ thống (kết thúc ở 100%). Kết quả có thể được xem ảnh chụp màn hình đính kèm.
Vấn đề không. 1: Trông không giống như nó phải trông.
Vấn đề không. 2: Mặc dù áp dụng zIndex -1 cho lớp phủ, ScrollView sẽ không cuộn nữa ngay sau khi lớp phủ được áp dụng.
Bất kỳ ý tưởng làm thế nào để đạt được điều này? Cảm ơn!
Đây là mã của tôi:
struct HealthExportPreview: View {
@ObservedObject var carbsEntries: CarbsEntries
var body: some View {
ScrollView(.horizontal) {
HStack {
ForEach(0..<self.carbsEntries.carbsRegime.count, id: \.self) { index in
ChartBar(carbsEntries: self.carbsEntries, entry: self.carbsEntries.carbsRegime[index], requiresTimeSplitting: index == self.carbsEntries.timeSplittingAfterIndex)
}
}
.padding()
.animation(.interactiveSpring())
}
.overlay(Rectangle()
.fill(
LinearGradient(gradient: Gradient(stops: [
.init(color: .clear, location: 0.8),
.init(color: Color(UIColor.systemBackground), location: 1.0)
]), startPoint: .leading, endPoint: .trailing)
)
.zIndex(-1)
)
.frame(height: CGFloat(carbsEntries.previewHeight + 80))
.onAppear() {
self.carbsEntries.fitCarbChartBars()
}
}
}
struct ChartBar: View {
var carbsEntries: CarbsEntries
var entry: (date: Date, carbs: Double)
var requiresTimeSplitting: Bool
static var timeStyle: DateFormatter {
let formatter = DateFormatter()
formatter.timeStyle = .short
return formatter
}
var body: some View {
VStack {
Spacer()
Text(FoodItemViewModel.doubleFormatter(numberOfDigits: entry.carbs >= 100 ? 0 : (entry.carbs >= 10 ? 1 : 2)).string(from: NSNumber(value: entry.carbs))!)
.font(.footnote)
.rotationEffect(.degrees(-90))
.offset(y: self.carbsEntries.appliedMultiplier * entry.carbs <= 40 ? 0 : 40)
.zIndex(1)
if entry.carbs <= self.carbsEntries.maxCarbsWithoutSplitting {
Rectangle()
.fill(Color.green)
.frame(width: 15, height: CGFloat(self.carbsEntries.appliedMultiplier * entry.carbs))
} else {
Rectangle()
.fill(Color.green)
.frame(width: 15, height: CGFloat(self.carbsEntries.getSplitBarHeight(carbs: entry.carbs)))
.overlay(Rectangle()
.fill(Color(UIColor.systemBackground))
.frame(width: 20, height: 5)
.padding([.bottom, .top], 1.0)
.background(Color.primary)
.rotationEffect(.degrees(-10))
.offset(y: CGFloat(self.carbsEntries.getSplitBarHeight(carbs: entry.carbs) / 2 - 10))
)
}
if self.requiresTimeSplitting {
Rectangle()
.fill(Color(UIColor.systemBackground))
.frame(width: 40, height: 0)
.padding([.top], 2.0)
.background(Color.primary)
.overlay(Rectangle()
.fill(Color(UIColor.systemBackground))
.frame(width: 20, height: 5)
.padding([.bottom, .top], 1.0)
.background(Color.black)
.rotationEffect(.degrees(80))
.offset(x: 20)
.zIndex(1)
)
} else {
Rectangle()
.fill(Color(UIColor.systemBackground))
.frame(width: 40, height: 0)
.padding([.top], 2.0)
.background(Color.primary)
}
Text(ChartBar.timeStyle.string(from: entry.date))
.fixedSize()
.layoutPriority(1)
.font(.footnote)
.rotationEffect(.degrees(-90))
.offset(y: 10)
.frame(height: 50)
.lineLimit(1)
}.frame(width: 30)
}
}
Trả lời
Ok, đã biết vấn đề SwiftUI là nó không chuyển một số cử chỉ qua lớp phủ thậm chí trong suốt.
Đây là cách tiếp cận khả thi để giải quyết vấn đề này - ý tưởng là có gradient chỉ bao phủ vị trí cạnh nhỏ, vì vậy phần khác của chế độ xem cuộn có thể được truy cập trực tiếp (vâng, dưới gradient nó sẽ vẫn không thể kéo được, nhưng nó là một phần nhỏ).
Demo được chuẩn bị và thử nghiệm với Xcode 11.7 / iOS 13.7
(biến thể đơn giản của chế độ xem của bạn)
struct HealthExportPreview: View {
var body: some View {
GeometryReader { gp in
ZStack {
ScrollView(.horizontal) {
HStack {
// simplified content
ForEach(0..<20, id: \.self) { index in
Rectangle().fill(Color.red)
.frame(width: 40, height: 80)
}
}
.padding()
.animation(.interactiveSpring())
}
// inject gradient at right side only
Rectangle()
.fill(
LinearGradient(gradient: Gradient(stops: [
.init(color: Color(UIColor.systemBackground).opacity(0.01), location: 0),
.init(color: Color(UIColor.systemBackground), location: 1)
]), startPoint: .leading, endPoint: .trailing)
).frame(width: 0.2 * gp.size.width)
.frame(maxWidth: .infinity, alignment: .trailing)
}.fixedSize(horizontal: false, vertical: true)
}
}
}