SwiftUI - ลบ ScrollView

Sep 13 2020

ฉันมีแผนภูมิขนาดใหญ่ที่สร้างขึ้นซึ่งฉันใส่ไว้ใน ScrollView เพื่อให้ผู้ใช้สามารถเลื่อนไปทางขวาและดูค่าทั้งหมดได้ ฉันต้องการแจ้งให้ผู้ใช้ทราบว่ามี "อีกมากมายที่จะมา" ทางด้านขวาโดยการทำให้ ScrollView จางหายไป บางสิ่งบางอย่างใน Swift เป็นเรื่องง่ายโดยใช้ CAGradientLayer

แนวทางของฉันคือใช้การซ้อนทับด้วยการไล่ระดับสีจากชัดเจน (เริ่มต้นที่ 80%) กับสีพื้นหลังของระบบ (สิ้นสุดที่ 100%) สามารถดูผลลัพธ์ได้จากภาพหน้าจอที่แนบมา

ฉบับที่ 1: ดูไม่เหมือนที่ควรจะเป็น

ฉบับที่ 2: แม้จะใช้ zIndex ของ -1 กับภาพซ้อนทับ แต่ ScrollView จะไม่เลื่อนอีกต่อไปทันทีที่ใช้ภาพซ้อนทับ

มีความคิดอย่างไรที่จะบรรลุสิ่งนี้? ขอบคุณ!

นี่คือรหัสของฉัน:

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)
    }
}

คำตอบ

2 Asperi Sep 13 2020 at 18:19

ตกลงเป็นที่ทราบกันดีว่าปัญหา SwiftUI ไม่ส่งผ่านท่าทางบางอย่างผ่านการซ้อนทับแม้กระทั่งโปร่งใส

นี่เป็นแนวทางที่เป็นไปได้ในการแก้ปัญหานี้ - แนวคิดคือการมีการไล่ระดับสีเพื่อให้ครอบคลุมเฉพาะตำแหน่งขอบเล็ก ๆ ดังนั้นส่วนอื่น ๆ ของมุมมองการเลื่อนจึงสามารถเข้าถึงได้โดยตรง (ใช่ภายใต้การไล่ระดับสีจะยังไม่สามารถลากได้ แต่เป็นส่วนเล็ก ๆ )

สาธิตเตรียมและทดสอบด้วย Xcode 11.7 / iOS 13.7

(รูปแบบที่เรียบง่ายของมุมมองของคุณ)

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)
        }
    }
}