SwiftUI ScrollView停止在带有渐变的Rectangle下的ZStack中工作

时间:2019-12-26 21:08:33

标签: swiftui

我遇到的问题是,当我在带有渐变填充的ScrollView上放置一个矩形时,scrollview停止对触摸做出反应。

目的是淡化滚动视图底部的项目,以免它们与父视图中的自定义底部导航视图冲突。

我尝试使用.frame修饰符使渐变高度仅位于底部四分之一左右,以希望不再阻止滚动视图,但是它没有用。

有人知道解决这个问题的方法吗?

import Foundation
import SwiftUI
import CoreData


struct TestView: View {

    var managedObjectContext:NSManagedObjectContext
    var spendings:FetchedResults<Spending>
    var expenses:FetchedResults<Expense>
    var settings:FetchedResults<Settings>
    @State private var showAddSpending:Bool = false

       @Binding var selection:Int

    var body: some View{
        VStack{
            HStack{
                Text("Spending").padding()

            }.padding(.horizontal)

            ZStack{

                //List items
                ScrollView{
                    ForEach(self.spendings) { spend in
                        //if(spend.currentMonth == true){
                        HStack{
                            // IS EXPANDED
                            if spend.isExpanded {

                                VStack{

                                    HStack{
                                        //NAME
                                        if(spend.currentMonth){
                                            Text("\(spend.name)")
                                                .lineLimit(1)
                                                .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
                                                .foregroundColor(Color .orange)
                                                .onLongPressGesture {
                                                    spend.currentMonth.toggle()
                                            }
                                        } else {

                                            Text("\(spend.name)")
                                                .lineLimit(1)
                                                .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
                                                .onLongPressGesture {
                                                    spend.currentMonth.toggle()
                                            }
                                        }

                                        //AMOUNT
                                        Text("\(spend.amount)")


                                            .frame(minWidth: 0, maxWidth: 70, alignment: .trailing)

                                        //DELETE
                                        DeleteStyle(text: "multiply", symbol: true)
                                            .onTapGesture {
                                                self.managedObjectContext.delete(spend)
                                                do {
                                                    try self.managedObjectContext.save()
                                                }catch{
                                                    print(error)
                                                }
                                        }
                                    }
                                    VStack{


                                        //CATEGORY
                                        Text("Category: \(spend.category)")


                                            .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)

                                    }
                                }

                                .onTapGesture {
                                    spend.isExpanded.toggle()
                                }





                            } else {

                                // ISNT EXPANDED
                                HStack{
                                    //NAME
                                    if(spend.currentMonth){
                                        Text("\(spend.name)")
                                            .lineLimit(1)
                                            .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
                                            .foregroundColor(Color .orange)
                                            .onLongPressGesture {
                                                spend.currentMonth.toggle()
                                        }
                                    } else {

                                        Text("\(spend.name)")
                                            .lineLimit(1)
                                            .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
                                            .onLongPressGesture {
                                                spend.currentMonth.toggle()
                                        }

                                    }

                                    Spacer()

                                    //AMOUNT
                                    Text("\(spend.amount)")


                                        .frame(minWidth: 0, maxWidth: 70, alignment: .trailing)


                                    //DELETE
                                    DeleteStyle(text: "multiply", symbol: true).onTapGesture {
                                        self.managedObjectContext.delete(spend)
                                        do {
                                            try self.managedObjectContext.save()
                                        }catch{
                                            print(error)
                                        }
                                    }
                                }

                                .onTapGesture {
                                    spend.isExpanded.toggle()
                                }


                            }
                        }
                    }
                    .foregroundColor(Color (UIColor .secondaryLabel))
                }.padding(.horizontal)

                //Button to add new item
                VStack{
                    Spacer()
                    HStack{
                        Spacer()
                        /*
                         Text("Total: £\(calculateTotalSpendingForCurrentMonth())")
                         .foregroundColor(.white)
                         .padding(15)
                         .background(Color .orange)
                         .cornerRadius(40)
                         */
                        if spendings.isEmpty {
                            HStack{
                                Text("Record a spend")
                                Image(systemName: "arrow.right")
                            }
                            .foregroundColor(Color (UIColor .secondaryLabel))
                            .padding(.bottom, 90)
                            .padding(.horizontal, 40)
                        }
                        VStack{
                            Button(action: {
                                self.showAddSpending = true
                            }) {
                                NavStyle(text: "plus", symbol: true)

                            }.sheet(isPresented: $showAddSpending) {
                                AddSpendingView(managedObjectContext: self.managedObjectContext, spendings: self.spendings, expenses: self.expenses)
                            }

                        }

                    }

                }

                //Black Fade at bottom
                VStack{
                    Spacer()
                    Rectangle()
                        .fill (
                            LinearGradient(gradient: Gradient(colors: [.clear, .black]),
                                           startPoint: .center, endPoint: .bottom)
                    )
                }
            }
        }.background(Color (UIColor.secondarySystemBackground))
    }
}

5 个答案:

答案 0 :(得分:1)

ScrollView本身是透明的,因此您可以使用渐变作为背景,就像下面的示例一样

ScrollView {
    ForEach(0..<100) { i in
        Text("Item \(i)")
    }
}
.background(LinearGradient(gradient: Gradient(colors: [.clear, .black]),
    startPoint: .center, endPoint: .bottom))

对于叠加层,有时您可以将以下修饰符与值false配合使用来进行触摸

public func allowsHitTesting(_ enabled: Bool) -> some View

但是对于ScrollView,即使它传递了轻击手势事件,也不能完全适用,因为滚动会被阻止。

通过Xcode 11.2.1 / iOS 13.2测试

答案 1 :(得分:1)

确实,SwiftUI View仍然不适合解决此问题。但是我找到了一个解决方案,没有我想要的那么漂亮。

首先,我尝试了1)通过UIViewRepresentable用SwiftUI-View包装UIView-gradient-overlay和2)在SwiftUI.body()中使用此包装器。但是结果与使用SwiftUI-View的结果相同。

因此,有一个反向解决方案-1)使用UIHostingController将SwiftUI内容转换为UIViewController,以及2)通过SwiftUI.body()中的UIViewControllerRepresentable使用此持有人控制器的转换

struct ContentView: View {

    var body: some View {

        UIViewControllerWrapper()
            .frame(maxWidth: .infinity, maxHeight: .infinity)
    }
}

====

struct UIViewControllerWrapper: UIViewControllerRepresentable {

    func makeUIViewController(context: Context) -> MyViewController {
        return MyViewController()
    }

    func updateUIViewController(_ uiViewController: MyViewController, context: Context) {}
}

====

class MyViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // SwiftUI content wrapper
        let swiftUIContent = UIHostingController(rootView: SwiftUIContent())

        swiftUIContent.view.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(swiftUIContent.view)

        swiftUIContent.view.leadingAnchor.constraint(
        equalTo: view.leadingAnchor).isActive = true
        swiftUIContent.view.trailingAnchor.constraint(
        equalTo: view.trailingAnchor).isActive = true
        swiftUIContent.view.topAnchor.constraint(
        equalTo: view.topAnchor).isActive = true
        swiftUIContent.view.bottomAnchor.constraint(
        equalTo: view.bottomAnchor).isActive = true
        swiftUIContent.didMove(toParent: self)


        // UIKit view overlay
        let viewOverlay = UIView(frame: UIScreen.main.bounds)

        let gradient = CAGradientLayer()
        gradient.frame = view.bounds
        gradient.colors = [UIColor.red.withAlphaComponent(0.5).cgColor, UIColor.green.withAlphaComponent(0.5).cgColor]
        viewOverlay.layer.insertSublayer(gradient, at: 0)

        viewOverlay.isUserInteractionEnabled = false
        view.addSubview(viewOverlay)

    }

}

====

struct SwiftUIContent: View{

    var body: some View {

        let body =

            ZStack{
//                ScrollView {
                List{
                    ForEach(0..<100) { i in
                        Text("Item \(i)")
                            .gesture(TapGesture()
                                    .onEnded({ _ in
                                        print("Item \(i)")
                            }))
                    }
                }
            }

        return body
    }
}

=====

所以滚动和录音效果很好!!享受吧!

答案 2 :(得分:1)

理想情况下,可以使用overlayallowsHitTesting(false)的滚动视图顶部显示渐变,以便用户仍然可以滚动,但这目前不起作用(可能是SwiftUI错误)。

但是我能够按照您的建议使用ZStack和frame来完成这项工作。这是ScrollView的直接替代品,其中包括一个额外的参数fadeDistance,该参数确定淡出效果的高度(用于垂直滚动)和/或宽度(用于水平滚动):

struct FadingScrollView<Content: View>: View {
    let fadeDistance: CGFloat
    let axes: Axis.Set
    let showsIndicators: Bool
    let content: Content

    init(
        fadeDistance: CGFloat,
        _ axes: Axis.Set = .vertical,
        showsIndicators: Bool = true,
        @ViewBuilder content: () -> Content
    ) {
        self.fadeDistance = fadeDistance
        self.axes = axes
        self.showsIndicators = showsIndicators
        self.content = content()
    }

    var body: some View {
        ZStack(alignment: .bottomTrailing) {
            ScrollView(axes, showsIndicators: showsIndicators) {
                // Pad the content depending on the axes so that bottom or trailing
                // part of the content isn't faded when scrolling all the way to the end.
                if axes == .vertical {
                    HStack(spacing: 0) {
                        content
                        Spacer()
                    }
                    Spacer(minLength: fadeDistance)
                } else if axes == .horizontal {
                    HStack(spacing: 0) {
                        VStack(spacing: 0) {
                            content
                            Spacer()
                        }
                        Spacer(minLength: fadeDistance)
                    }
                } else {
                    HStack(spacing: 0) {
                        content
                        Spacer(minLength: fadeDistance)
                    }
                    Spacer(minLength: fadeDistance)
                }
            }

            if axes.contains(.vertical) {
                fadeGradient(for: .vertical)
                    .frame(height: fadeDistance)
                    .allowsHitTesting(false) // Maybe Apple will make this work in the future
            }


            if axes.contains(.horizontal) {
                fadeGradient(for: .horizontal)
                    .frame(width: fadeDistance)
                    .allowsHitTesting(false) // Maybe Apple will make this work in the future
            }
        }
    }

    private func fadeGradient(for axis: Axis) -> some View {
        LinearGradient(
            gradient: Gradient(colors: [
                Color(.systemBackground).opacity(0),
                Color(.systemBackground).opacity(1)
            ]),
            startPoint: axis == .vertical ? .top : .leading,
            endPoint: axis == .vertical ? .bottom : .trailing
        )
    }
}

答案 3 :(得分:1)

UIViewRepresentable.disabled(true) 结合使用允许与带有叠加层的 ScrollView 进行交互。

struct OverlayWrapper<Content: View>: UIViewRepresentable {
    
    var content: Content
    
    init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content()
    }
    
    func makeUIView(context: Context) -> UIView {
        
        let uiView = UIView()
        
        // Get reference to the SwiftUI view wrapped inside PagerScrollView
        let child = UIHostingController(rootView: content)
        
        let screenWidth = UIScreen.main.bounds.width
        let screenHeight = UIScreen.main.bounds.height
        
        // Get size of the child
        let newSize = child.view.sizeThatFits(CGSize(width: screenWidth, height: screenHeight))
        
        let frame = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height)
        
        child.view.frame = frame
        child.view.backgroundColor = UIColor.clear

        uiView.frame = frame
        uiView.backgroundColor = UIColor.clear
        
        uiView.addSubview(child.view)
        
        return uiView
    }
    
    
    func updateUIView(_ uiView: UIView, context: Context) {
        // If the UI is not updated, updateUIView will not be called
    }
}

使用 OverlayWrapper 的示例:

struct ExampleView: View {
    
    var body: some View {
        ZStack {
            ScrollView(.vertical) {
                VStack {
                    ForEach(0..<100) { i in
                        Text("\(i)")
                    }
                }
            }
            OverlayWrapper() {
                // This can be replaced with any SwiftUI view.
                Rectangle()
                    .frame(width: 200, height: 200)
                    .foregroundColor(Color.blue.opacity(0.5))
            }
            .frame(width: 200, height: 200, alignment: .center)
            .disabled(true)
        }
    }
}

答案 4 :(得分:0)

如果有人在看如何将滑动手势传递给 scrollView,.allowsHitTesting(false) 只会传递轻按手势

ZStack {
    ScrollView {
        Text("Lorem ipsum dolor sit amet, consectetuer...")
    }
    LinearGradient(gradient: Gradient(colors: [Color.black,
                                                       Color.clear]),
                           startPoint: .top,
                           endPoint: .bottom)
      .allowsHitTesting(false)
}

解决方案是使用 .mask 修饰符将所有手势传递给 scrollView

var body: some View {
        ScrollView {
                Text("Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate eget, arcu. In enim justo, rhoncus ut, imperdiet a, venenatis vitae, justo. Nullam dictum felis eu pede mollis pretium. Integer tincidunt. Cras dapibus. Vivamus elementum semper nisi. Aenean vulputate eleifend tellus. Aenean leo ligula, porttitor eu, consequat vitae, eleifend ac, enim. Aliquam lorem ante, dapibus in, viverra quis, feugiat a, tellus. Phasellus viverra nulla ut metus varius laoreet. Quisque rutrum. Aenean imperdiet. Etiam ultricies nisi vel augue. Curabitur ullamcorper ultricies nisi. Nam eget dui. Etiam rhoncus. Maecenas tempus, tellus eget condimentum rhoncus, sem quam semper libero, sit amet adipiscing sem neque sed ipsum. Nam quam nunc, blandit vel, luctus pulvinar, hendrerit id, lorem. Maecenas nec odio et ante tincidunt tempus. Donec vitae sapien ut libero venenatis faucibus. Nullam quis ante. Etiam sit amet orci eget eros faucibus tincidunt. Duis leo. Sed fringilla mauris sit amet nibh. Donec sodales sagittis magna. Sed consequat, leo eget bibendum sodales, augue velit cursus nunc")
                    .multilineTextAlignment(.center)
            }
        }
        .mask(
            LinearGradient(gradient: Gradient(colors: [Color.black,
                                                       Color.black,
                                                       Color.clear]),
                           startPoint: .top,
                           endPoint: .bottom)
        )
        .ignoresSafeArea(edges: .bottom)
    }
}

enter image description here