SwiftUI set blur on scrollview children except top most child - swift

How would I set a blur on all of a scrollview's children (inside a vstack) except for the topmost visible child in the scrollview?
I've tried something like the following but it does not work.
ScrollViewReader { action in
ScrollView {
LazyVStack(alignment: .leading) {
ForEach(lyrics, id: \.self) { lyric in
Text(lyric)
.onAppear {
blurOn = 0.0
}
.onDisappear {
blurOn = 5.0
}
.padding()
.blur(radius: blurOn)
}
}
.font(.system(size: 20, weight: .medium, design: .rounded))
.padding(.bottom)
}
.cornerRadius(10)
.frame(height: 130)
}
After thinking about it, I guess I could put the VStack in a ZStack and place a 0 opacity view on the bottom with a blur...just wondering if there's a better way to do this?

Related

How to make a clear SwiftUI view block the scrollview underneath it

So I have a ZStack that contains a ScrollView on the bottom and an HStack that's aligned at the top. It looks something like this:
ZStack {
ScrollView {
//Content
}
VStack {
HStack {
Spacer()
Circle()
.frame(width: 30, height: 30)
Spacer()
}
Spacer()
}
}
Now, I want that HStack to block any interaction with the ScrollView beneath it. I've noticed that when I set the background on the HStack to a non-clear color, it behaves as I'd like it to. However, if the background is clear, then the touches go through the HStack and interact with the ScrollView beneath it. I've also tried using .allowsHitTesting(true) on the HStack with no luck.
Any help is much appreciated!
https://i.stack.imgur.com/5Jzby.png
"...I've noticed that when I set the background on the HStack to a non-clear color, it behaves as I'd like it to.", then you could use Color.white.opacity(0.001)
instead of Color.clear or non-clear color
You can easily accomplish this by putting it in a VStack instead of a ZStack. The touches will be ignored as it's above the ScrollView. You can also have a look at .layoutPriority(), it may also be useful. Documentation is here.
struct ContentView: View {
var body: some View {
VStack {
HStack {
Spacer()
Circle()
.foregroundColor(Color.blue)
.frame(width: 30, height: 30)
Spacer()
}
.padding()
// or this does the same thing as the HStack two spacers
/*
Circle()
.foregroundColor(Color.blue)
.frame(width: 30, height: 30)
.frame(minWidth: 0, maxWidth: .infinity)
*/
Spacer()
ScrollView {
ForEach(1..<11) { value in
Text("\(value)")
}
}
}
}
}

Spacer() swiftui leaves a white space

I'm currently building a UI that has a big red 250px height box on the top that holds information and a scrollview underneath it. I build this all in a VStack(). I put a spacer() at the bottom of the VStack to push everything to the top of the screen. But it has a big white space between the red container at the top and the scrollview. Even though both don't have any padding(). Does anyone know how this is possible? This is my code:
import SwiftUI
struct User: View {
let columns = [
GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible())
]
var body: some View {
NavigationView {
VStack {
...
code
...
}
.frame(width: UIScreen.main.bounds.width, height: 250)
.background(Color.red)
.ignoresSafeArea()
// somehow it has a white space here...
ScrollView {
LazyVGrid(columns: columns, spacing: 2) {
ForEach((0...56), id: \.self) {_ in
Image("testimage")
.resizable()
.scaleEffect(CGSize(width: 1.0, height: 2.0))
.scaledToFit()
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 50)
.cornerRadius(10)
}.padding(.bottom, 75)
}
.frame(width: UIScreen.main.bounds.width)
.padding(.top, -10)
}
.navigationBarTitle("")
.navigationBarBackButtonHidden(true)
.navigationBarHidden(true)
// this Spacer should push everything to the top
Spacer()
}
}
}
.navigationBarTitle("")
.navigationBarBackButtonHidden(true)
.navigationBarHidden(true)
}
}
Edit:
To make my question better understandable I made this summery:
I have build a VStack with 2 items in it. there is a big white space between the two and I don't know how to get rid of this white space. If I remove .ignoresSafeArea() the white space disappears. The thing is I need .ignoresSafeArea() to push the first item in VStack up. Does anyone know what I can do to keep .ignoresSafeArea() but get rid of the white space?
I added a screenshot of the problem. I gave the scrollview a green background so that the white spacing between the VStack and the Scrollview is visable.
You need to embed everything in a VStack with 0 spacing.
import SwiftUI
struct User: View {
let columns = [
GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible())
]
var body: some View {
NavigationView {
VStack(spacing: 0) {
VStack {
Text("Hello World!") //Easier to test
}
.frame(height: 250) //Dont use UIScreen
.background(Color.red)
.ignoresSafeArea()
// somehow it has a white space here...
ScrollView {
LazyVGrid(columns: columns, spacing: 2) {
ForEach((0...56), id: \.self) {_ in
Image("testimage")
.resizable()
.scaleEffect(CGSize(width: 1.0, height: 2.0))
.scaledToFit()
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 50)
.cornerRadius(10)
}.padding(.bottom, 75)
}
.frame(width: UIScreen.main.bounds.width)
.padding(.top, -10)
}
Spacer()
}
.navigationBarTitle("")
.navigationBarBackButtonHidden(true)
.navigationBarHidden(true)
}
}
}
Also, you might need to give the ScrollView a max height to push it upwards. .frame(maxHeight: 175)
As you haven't given a reproducible example or an image. I cant tell you for certain what will work
I guess Arnavs Answer is correct.
Notice:
When you are trying to implement multiple Views on one page, 90% of the time it is better to have them in one H/V/Z-Stack.
For a better understanding start a new project and try ...
Navigationview{
VStack{
Text("Hello Vertical World")
Text("Hello Vertical World2")
}
HStack{
Text("Hello Horizontal World")
Text("Hello Horizontal World2")
}
}
and the same thing but "different"
NavigationView {
VStack{
Text("Hello Vertical World")
Text("Hello Vertical World2")
HStack{
Text("Hello Horizontal World")
Text("Hello Horizontal World2")
}
}
}
I can't double check bc i am on my phone, but this should look very different.
I fixed the issue by putting the VStack and the Scrollview in an other VStack in the navigationView. Then I put the .ignoresSafeArea() on this new VStack.
Code:
import SwiftUI
struct User: View {
let columns = [
GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible())
]
var body: some View {
NavigationView {
// new VStack that includes the topbar VStack, the
// scrollview and has the .ignoresSafeArea() on it.
VStack {
VStack {
...
Code topbar
...
}
.frame(width: UIScreen.main.bounds.width, height: 250)
.background(Color.red)
Scrollview {
...
Code scrollview
...
}
Spacer()
}
.ignoresSafeArea()
}
.navigationBarTitle("")
.navigationBarBackButtonHidden(true)
.navigationBarHidden(true)
}
}

SwiftUI List/Form/ScrollView being clipped when offset

I changed the y offset of a list, and now it's being clipped.
I am trying to make it so that when you scroll, you can partially see the text underneath the Title and buttons at the top of the view. In other words, I want the top section of the screen to be slightly transparent.
I added the offset to the list, so that it didn't overlap with the information at the top.
The image above is with the VStack in my code showing. I thought that the VStack might be getting in the way, so I commented it out and the image below was the result:
Here's my code:
var body: some View {
ZStack {
VStack {
HStack {
Button(action: {self.showAccountView.toggle()}) {
Image(systemName: "person.fill")
.renderingMode(.original)
.font(.system(size: 20, weight: .bold))
.frame(width: 44, height: 44)
.modifier(NavButtons())
}
.sheet(isPresented: $showAccountView) {
AccountView()
}
Spacer()
Button(action: { self.showHelpCenter.toggle()}) {
Image(systemName: "questionmark")
.renderingMode(.original)
.font(.system(size: 20, weight: .bold))
.modifier(NavButtons())
}
.sheet(isPresented: $showHelpCenter) {
HelpCenter()
}
}
.frame(maxWidth: .infinity)
.padding(.horizontal)
Spacer()
}
List {
ForEach (store.allLogs) { thing in
VStack (alignment: .leading) {
HStack {
Text("\(thing.date) , \(thing.time)")
}
Text(thing.notes)
.multilineTextAlignment(.leading)
}
}
}.offset(y: 50)
}
}
EDIT:
This is one possible solution:
struct MyList: UIViewRepresentable {
func makeUIView(context: Context) -> UITableView {
let view = UITableView()
view.clipsToBounds = false
return view
}
func updateUIView(_ uiView: UITableView, context: Context) {
}
}
and then you would use makeUIView and updateUIView to update the cells. This is just messy and it's not really using SwiftUI at that point.
Second Edit
I've found this issue with a scrollView as well as a form:
The black is the background. Here's the code for all three:
Group {
List ((0 ... 10), id: \.self) {
Text("Row \($0)")
}
.offset(y: 200)
.border(Color.blue, width: 3)
.background(Color.black)
ScrollView {
Text("Text")
}
.foregroundColor(.white)
.offset(y: 200)
.border(Color.blue, width: 3)
.background(Color.black)
Form {
Text("Text")
}
.offset(y: 200)
.border(Color.blue, width: 3)
.background(Color.black)
}
Here are the wireframes of a List:
Here are the names of frames that are the same height as the List/Form/ScrollView:
List:
PlainList.BodyContent
ListCore.Container
ListRepresentable
View Host
TableWrapper
UpdateCoalescingTableView
Form:
GroupList.BodyContent
ListCore.Container
ListRepresentable
View Host
TableWrapper
UpdateCoalescingTableView
ScrollView:
ScrollViewBody
SystemScrollView
View Host
HostingScrollView
I guess that my question has changed from "how do I do this..." to "Why is this happening?"
I'm pretty confused about what exactly is going on.
UIScrollView.appearance().clipsToBounds = false
Stick this in the body of AppDelegate.swift -> applicationDidFinishLaunchingWithOptions(). It will make all scroll views across your application unclipped by default.
The problem is that a list has a table view (which is a scroll view) underneath. Scroll views by default are clipped. We just need to change that default.
Using LazyHStack instead of HStack solves the clipping problem.

Modal picker not scrolling right SwiftUI

I created a modal but it seems to have a bug on the selection. When scrolling the left, it scrolls the right, I have to go to the very edge of the left to be able to scroll, this is how it looks:
import SwiftUI
struct ContentView: View {
#State var showingModal = false
#State var hours: Int = 0
#State var minutes: Int = 0
var body: some View {
ZStack {
VStack {
Button("Show me"){
self.showingModal = true
}
if $showingModal.wrappedValue {
VStack(alignment: .center) {
ZStack{
Color.black.opacity(0.4)
.edgesIgnoringSafeArea(.vertical)
// this one is it
VStack(spacing: 20) {
Text("Time between meals")
.bold().padding()
.frame(maxWidth: .infinity)
.background(Color.yellow)
.foregroundColor(Color.white)
HStack {
Spacer()
VStack {
Picker("", selection: $hours){
ForEach(0..<4, id: \.self) { i in
Text("\(i) hours").tag(i)
}
}
.frame(width: 150, height: 120)
.clipped()
}
VStack {
Picker("", selection: $minutes){
ForEach(0..<60, id: \.self) { i in
Text("\(i) min").tag(i)
}
}
.frame(width: 150, height: 120)
.clipped()
}
}
Spacer()
Button(action: {
self.showingModal = false
}){
Text("Close")
} .padding()
}
.frame(width:300, height: 300)
.background(Color.white)
.cornerRadius(20).shadow(radius: 20)
}
}
}
}
}
}
}
How can I fix that little bug? I tried playing around with the layout but no use... any help would be appreciated
What if I told you the reason your Picker not working was this line?
.cornerRadius(20).shadow(radius: 20)
Unfortunately, SwiftUI is still quite buggy and sometimes it doesn't do what it is supposed to do and especially Pickers are not that reliable. I guess we'll need to wait and see the next iteration of SwiftUI, but for now you can replace that line with the code below:
.mask(RoundedRectangle(cornerRadius: 20))
.shadow(radius: 20)
There are just modifiers which affect all view hierarchy (ie. all subviews) that can change resulting layout/presentation/behaviour. And .cornerRadius and .shadow are such type modifiers.
The solution is to apply (as intended) those modifiers only to entire constructed view, and here it is
.compositingGroup() // <<< fix !!
.cornerRadius(20).shadow(radius: 20)
where .compositionGroup is intended to make above view hierarchy flat rendered and all below modifiers applied to only to that flat view.

SwiftUI ScrollView only scroll in one direction

Trying to make a custom list using a view as the list row style (to get rid of the ugly line separates in the list by default).
However, once I put my ZStack rows inside a scroll view, the scroll view scrolls in both directions and not just vertically.
Here is the contentView:
NavigationView {
ScrollView{
VStack(alignment: .leading){
ForEach(friends) { friends in
NavigationButton(destination: MessageDetailView(friend: friends)) {
CustomListItem(friend: friends)
}
}
Spacer()
}
}
.foregroundColor(.black)
.navigationBarTitle(Text("Messages"))
}
and here is the customListItem:
Group{
ZStack {
RoundedRectangle(cornerRadius: 10)
.shadow(radius: 1, y:1)
.frame(width: UIScreen.main.bounds.width - 32, height: 75)
.foregroundColor(.gray)
HStack {
Image(systemName: "person.crop.circle")
.resizable()
.frame(width: 50, height: 50)
VStack(alignment: .leading) {
HStack {
Text(friend.name)
.font(.headline)
Text("\(friend.date, formatter: dateFormatter)")
}
Text(friend.messagesReceived[0])
.font(.subheadline)
} .lineLimit(nil)
Spacer()
Image(systemName: "chevron.right.circle")
.foregroundColor(.black)
}.padding(10)
}.padding([.leading, .trailing])
}
Is there any way I can limit the scrolling to vertical or force a frame on this?
Trying to use the .frame(...) modifier does not work as I've tried it. This results in the view not loading at all.
Example Images:
As of Xcode 11 beta 3 (11M362v) there is an axes parameter when constructing a ScrollView which can be set to .horizontal or .vertical
ScrollView(.vertical, showsIndicators: true) { ... }
This can be achieved with a GeometryReader. Wrap your ScrollView in one and then set the width of your VStack.
GeometryReader is a super easy, and pretty useful trick to have in your belt for SwiftUI :)
Here's how I got it working with your code:
NavigationView {
GeometryReader { geometry in
ScrollView {
VStack(alignment: .leading){
ForEach(self.friends) { friend in
NavigationButton(destination: MessageDetailView(friend: friend)) {
CustomListItem(friend: friend)
}
}
Spacer()
}.frame(width: geometry.size.width)
}
.foregroundColor(.black)
.navigationBarTitle(Text("Messages"))
}
}
Using iOS 14, Swift 5, SwiftUI 2.0
This is the answer... you can use both vertical and horizontal in a set.
ScrollView([.vertical,.horizontal]) {
....
}
Although in most cases the above answers works but none of them did work for me!
In my case, the problem was the scrollView children which were wider than the scrollView!
adding a static width to them did the trick.
Try being more specific with the scroll direction like this
ScrollView(.vertical) {}