Placing a rectangle in front of ScrollView affects scrolling
Asked Answered
L

2

2

I need to place a translucent rectangle on front of ScrollView but when i put everything (Rectangle & ScrollView) inside of a ZStack, scroll & touch events stop working within this rectangle.

Atm I'm using .background modifier as it doesn't affect scrolling but I am still looking for way to make it work properly with rectangle placed over (in front of) my ScrollView.

Is there any way to put a View over ScrollView so it wouldn't affect it's functionality?

Here's the code i'm using now (i changed the colors and removed opacity to make the objects visible as my original rectangle is translucent & contains barely visible gradient)

struct ContentView: View {
    
    var body: some View {
        ZStack {
            
            ScrollView {
                LazyVStack(spacing: 0) {
                    ForEach(0...100, id:\.self) { val in
                        ZStack {
                            Text("test")
                                .font(.system(size: 128))
                        } // ZStack
                        .background(Color.blue)
                    } // ForEach
                }
            }
            .background(RadialGradient(gradient: Gradient(stops: [
                                                            .init(color: Color.blue, location: 0),
                                                            .init(color: Color.red, location: 1)]), center: .top, startRadius: 1, endRadius: 200)
                            .mask(
                                VStack(spacing: 0) {
                                    Rectangle()
                                        .frame(width: 347, height: 139)
                                        .padding(.top, 0)
                                    Spacer()
                                }
                            ))
            
        }
    }
}
Lebel answered 6/1, 2021 at 11:22 Comment(0)
L
0

I decided to post a solution here.. it's based on an approach suggested by Asperi.

2Asperi: Thank you, i appreciate your help, as always.

I played a little bit with applying .opacity & mask to Blur but it didn't work.

So i applied mask to the .layer property inside makeUIView and it worked fine

import SwiftUI

struct ContentView: View {
    
    var body: some View {
        ZStack {
            ZStack {
            ScrollView {
                LazyVStack(spacing: 0) {
                    ForEach(0...100, id:\.self) { val in
                        ZStack {
                            Text("test")
                                .font(.system(size: 128))
                        } // ZStack
                        .background(Color.white)
                    } // ForEach
                }
            }
                Blur(style: .systemThinMaterial)
                                .mask(
                                    VStack(spacing: 0) {
                                        Rectangle()
                                            .frame(width: 347, height: 139)
                                            .padding(.top, 0)
                                        Spacer()
                                    }
                                )
                    .allowsHitTesting(false)
            }
        }
    }
}

struct Blur: UIViewRepresentable {
    var style: UIBlurEffect.Style = .systemMaterial
    func makeUIView(context: Context) -> UIVisualEffectView {
        
        let blurEffectView = UIVisualEffectView(effect: UIBlurEffect(style: style))
        
        let gradientMaskLayer = CAGradientLayer()
        
        gradientMaskLayer.type = .radial
        
        gradientMaskLayer.frame = CGRect(x: 0, y: 0, width: 347, height: 256)
        
        gradientMaskLayer.colors = [UIColor.black.cgColor, UIColor.clear.cgColor]
        
        gradientMaskLayer.startPoint = CGPoint(x: 0.5, y: 0)
        gradientMaskLayer.endPoint = CGPoint(x: 1, y: 1)
        
        gradientMaskLayer.locations = [0 , 0.6]
        blurEffectView.layer.mask = gradientMaskLayer
        
        return blurEffectView
    }
    func updateUIView(_ uiView: UIVisualEffectView, context: Context) {
        uiView.effect = UIBlurEffect(style: style)
    }
}

The only thing i don't understand is why startPoint and endPoint work only when i set them to [0.5,0] & [1,1] but not [0.5,0] & [0.5,1] - i expected that it should determine the direction of radial gradient and in my case it should go from .topCenter to .topBottom (which it does but i don't understand the nature of endPoint)..

Lebel answered 6/1, 2021 at 14:41 Comment(0)
S
3

Here is a possible approach to start with - use UIVisualEffectView. And Blur view is taken from How do I pass a View into a struct while getting its height also? topic.

demo

struct ScrollContentView: View {
    
    var body: some View {
        ZStack {
            
            ScrollView {
                LazyVStack(spacing: 0) {
                    ForEach(0...100, id:\.self) { val in
                        ZStack {
                            Text("test")
                                .font(.system(size: 128))
                        } // ZStack
                        .background(Color.blue)
                    } // ForEach
                }
            }
            Blur(style:  .systemThinMaterialLight)
                .mask(
                    VStack(spacing: 0) {
                        Rectangle()
                            .frame(width: 347, height: 139)
                            .padding(.top, 0)
                        Spacer()
                    }
                )
                .allowsHitTesting(false)
        }
    }
}
Saloma answered 6/1, 2021 at 11:40 Comment(1)
thank you! ;) i posted updated code below. it works well;)Lebel
L
0

I decided to post a solution here.. it's based on an approach suggested by Asperi.

2Asperi: Thank you, i appreciate your help, as always.

I played a little bit with applying .opacity & mask to Blur but it didn't work.

So i applied mask to the .layer property inside makeUIView and it worked fine

import SwiftUI

struct ContentView: View {
    
    var body: some View {
        ZStack {
            ZStack {
            ScrollView {
                LazyVStack(spacing: 0) {
                    ForEach(0...100, id:\.self) { val in
                        ZStack {
                            Text("test")
                                .font(.system(size: 128))
                        } // ZStack
                        .background(Color.white)
                    } // ForEach
                }
            }
                Blur(style: .systemThinMaterial)
                                .mask(
                                    VStack(spacing: 0) {
                                        Rectangle()
                                            .frame(width: 347, height: 139)
                                            .padding(.top, 0)
                                        Spacer()
                                    }
                                )
                    .allowsHitTesting(false)
            }
        }
    }
}

struct Blur: UIViewRepresentable {
    var style: UIBlurEffect.Style = .systemMaterial
    func makeUIView(context: Context) -> UIVisualEffectView {
        
        let blurEffectView = UIVisualEffectView(effect: UIBlurEffect(style: style))
        
        let gradientMaskLayer = CAGradientLayer()
        
        gradientMaskLayer.type = .radial
        
        gradientMaskLayer.frame = CGRect(x: 0, y: 0, width: 347, height: 256)
        
        gradientMaskLayer.colors = [UIColor.black.cgColor, UIColor.clear.cgColor]
        
        gradientMaskLayer.startPoint = CGPoint(x: 0.5, y: 0)
        gradientMaskLayer.endPoint = CGPoint(x: 1, y: 1)
        
        gradientMaskLayer.locations = [0 , 0.6]
        blurEffectView.layer.mask = gradientMaskLayer
        
        return blurEffectView
    }
    func updateUIView(_ uiView: UIVisualEffectView, context: Context) {
        uiView.effect = UIBlurEffect(style: style)
    }
}

The only thing i don't understand is why startPoint and endPoint work only when i set them to [0.5,0] & [1,1] but not [0.5,0] & [0.5,1] - i expected that it should determine the direction of radial gradient and in my case it should go from .topCenter to .topBottom (which it does but i don't understand the nature of endPoint)..

Lebel answered 6/1, 2021 at 14:41 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.