How can we Blur a Shape with Gradient in SwiftUI? - gradient

Was trying to blur my circle with a gradient can't figure out why it doesn't work. Anybody have ideas or is this not supported yet?
struct GradientExperiment: View {
var body: some View {
VStack {
//blur works
Circle()
.fill(Color(.blue))
.frame(width: 50, height: 50)
.blur(radius: 10)
//blur does not work
Circle()
.fill(LinearGradient(gradient: Gradient(colors: [.blue,.red]), startPoint: .bottom, endPoint: .top))
.frame(width: 50, height: 50)
.blur(radius: 10)
}
}
}

Add a mask to your LinearGradient and use the Circle as a mask:
LinearGradient(gradient: Gradient(colors: [.blue, .red]), startPoint: .bottom, endPoint: .top)
.mask(
Circle()
.frame(width: 50, height: 50)
.blur(radius: 10)
)

Related

Leave a space between the corner of the phone and the progress bar in a swiftui

I want the space progress bar between the edge of the phone to be 24. I want the size of the space to be 24. I also want the text to be aligned with the end of the bar.
my code is like this:
struct SeasonsDetailsProgressBar: View {
var bar : Float = 0.5
var body: some View {
GeometryReader{ proxy in
HStack(spacing: 22){
Text("Step")
.bold()
.font(.system(size: 12))
Spacer()
VStack(alignment: .leading){
HStack{
Text("Total steps taken")
.font(.system(size: 10))
Spacer()
Text("3.214.629 / 5.000.0000")
.font(.system(size: 10))
}
ZStack(alignment:.leading){
RoundedRectangle(cornerRadius: 4, style: .continuous)
.frame(width: proxy.size.width, height: 4)
.foregroundColor(Color.black.opacity(0.1))
RoundedRectangle(cornerRadius: 4, style: .continuous)
.frame(width: proxy.size.width * CGFloat(bar), height: 4)
.foregroundColor(Color.init(red: 0.965, green: 0.224, blue: 0.49))
}.padding(.trailing ,
proxy.size.width * 0.9)
}
}
.padding(.leading, 25)
.padding(.trailing, 25)
}
}
}
proxy.size.width gives us a width of screen you have to minus size of label and spacing between them.
struct SeasonsDetailsProgressBar: View {
var bar : Float = 0.5
var body: some View {
GeometryReader{ proxy in
HStack(spacing: 0) { // -> No spacing
Text("Step")
.bold()
.font(.system(size: 12))
.frame(width: 50, alignment: .leading) // exact width of "Step" label
.padding(.leading, 24). // exactly 24 from left side
VStack(alignment: .leading){
HStack{
Text("Total steps taken")
.font(.system(size: 10))
Spacer()
Text("3.214.629 / 5.000.0000")
.font(.system(size: 10))
.padding(.trailing, 24) // exactly 24 from left side
}
ZStack(alignment:.leading){
RoundedRectangle(cornerRadius: 4, style: .continuous)
.frame(width: (proxy.size.width - 98), height: 4) // width of screen -(24 left + 50 label + 24 right)
.foregroundColor(Color.black.opacity(0.1))
RoundedRectangle(cornerRadius: 4, style: .continuous) // width-98
.frame(width: (proxy.size.width - 98) * CGFloat(bar), height: 4)
.foregroundColor(Color.init(red: 0.965, green: 0.224, blue: 0.49))
}
}
}
} // no paddings
}
}
The problem is that you use top GeometryReader which is occupied all available space and its width is full screen width.
You need to use another GeometryReader inside your ZStack which width will be available space for your progress bar. Also you need to setup height, because otherwise it will occupied all available space like Spacer component
ZStack(alignment: .leading) {
GeometryReader { proxy in
RoundedRectangle(cornerRadius: 4, style: .continuous)
.frame(width: proxy.size.width, height: 4)
.foregroundColor(Color.black.opacity(0.1))
RoundedRectangle(cornerRadius: 4, style: .continuous)
.frame(width: proxy.size.width * CGFloat(bar), height: 4)
.foregroundColor(Color.init(red: 0.965, green: 0.224, blue: 0.49))
}.frame(height: 4)
}
And its better to use padding directly on your label and progress bar, finally it will be:
struct SeasonsDetailsProgressBar: View {
var bar : Float = 0.5
var body: some View {
HStack(spacing: 22) {
Text("Step")
.bold()
.font(.system(size: 12))
.padding(.leading, 24)
Spacer()
VStack(alignment: .leading) {
HStack{
Text("Total steps taken")
.font(.system(size: 10))
Spacer()
Text("3.214.629 / 5.000.0000")
.font(.system(size: 10))
}
ZStack(alignment:.leading) {
GeometryReader{ proxy in
RoundedRectangle(cornerRadius: 4, style: .continuous)
.frame(width: proxy.size.width, height: 4)
.foregroundColor(Color.black.opacity(0.1))
RoundedRectangle(cornerRadius: 4, style: .continuous)
.frame(width: proxy.size.width * CGFloat(bar), height: 4)
.foregroundColor(Color.init(red: 0.965, green: 0.224, blue: 0.49))
}.frame(height: 4)
}
}.padding(.trailing, 24)
}
}
}

Why does the blur modifier affect views differently?

I spent a lot of time trying to figure out whether this was a bug, but I couldn't for the life of me figure out why a shape wasn't blurring correctly.
VStack {
//Doesn't blur right
Capsule()
.fill(LinearGradient(colors: [Color.red, Color.blue], startPoint: .leading, endPoint: .trailing))
.frame(width: 80, height: 40)
.blur(radius: 10)
//Does blur correctly
HStack {
}
.frame(width: 80, height: 40)
.background(
LinearGradient(colors: [Color.red, Color.blue], startPoint: .leading, endPoint: .trailing)
)
.clipShape(Capsule())
.blur(radius: 10)
}
When you apply blur to an empty HStack, it blurs correctly, while using a blur to a shape doesn't. Can someone explain why it differs?
Firstly, two of them looks like the same but it's not the same.
From Apple docs, Capsule() is subclass of Shape which is subclass of View.
The first one, when you using Capsule() directly then you .fill means just like you add subview LinearGradient with colors into Capsule(). That's the reason I think .blur not working correctly when you call directly from Capsule().
The second one, you .blur correctly because the view was directly be drawn on view ( just like drawing in layer of view in normal UIKit)
The view hierarchy show the different between two of them.
The solution:
You can do like your second view
HStack {
}
.frame(width: 80, height: 40)
.background(
LinearGradient(colors: [Color.red, Color.blue], startPoint: .leading, endPoint: .trailing)
)
.clipShape(Capsule())
.blur(radius: 10)
Or you can make linear gradient with mask
HStack {
LinearGradient(gradient: Gradient(colors: [.red, .blue]), startPoint: .leading, endPoint: .trailing)
.mask(
Capsule()
.blur(radius: 10)
.frame(width: 80, height: 40)
)
}.frame(width: 200, height: 60)
Both will act the same like you draw on the layer of view
UPDATE
If you need it works directly, just use only one color then call .foregroundColor then blur will continue work - But only one color at the time only.
Capsule()
.foregroundColor(Color.red)
.blur(radius: 10)
.frame(width: 80, height: 40)

SwiftUI linear gauge

UPDATE
I was able to get something closer to what I'm trying to achieve. Still needs work. I attempted to use a rounded rectangle for the mask but doesn't look good. Any suggestions would be greatly appreciated.
GeometryReader { geometry in
RoundedRectangle(cornerRadius: 7.5, style: .circular)
.fill(LinearGradient(gradient: Gradient(colors: [Color.green, Color.yellow, Color.orange, Color.red, Color.purple]), startPoint: .bottom, endPoint: .top))
.frame(width: geometry.size.width, height: geometry.size.height, alignment: .bottomLeading)
.overlay(Color.black.opacity(0.35).cornerRadius(7.5))
RoundedRectangle(cornerRadius: 7.5, style: .circular)
.fill(LinearGradient(gradient: Gradient(colors: [Color.green, Color.yellow, Color.orange, Color.red, Color.purple]), startPoint: .bottom, endPoint: .top))
.frame(width: geometry.size.width, height: geometry.size.height, alignment: .bottomLeading)
.mask(
VStack {
Spacer()
Rectangle()
// Adjust value 1 to needs
.frame(width: geometry.size.width, height:geometry.size.height * (CGFloat(1) / CGFloat(11)), alignment: .bottom)
})
}
.frame(width: 15, height: .infinity)
.padding(.all, 10)
Older
I'm attempting to create a simple gauge like the linear gauge in watchOS. The trouble I'm having is figuring out the proper way to overlay the current value. The outcome I'd like to see is below. I'm sure there's a better way.
Outcome
Mine look absolutely terrible
ZStack {
LinearGradient(gradient: Gradient(colors: [Color.green, Color.yellow, Color.orange, Color.red, Color.purple]), startPoint: .bottom, endPoint: .top)
GeometryReader { metrics in
Circle()
.stroke(Color.white,lineWidth: 2)
.frame(minHeight: 0, maxHeight: metrics.size.height)
.foregroundColor(Color.clear)
.position(x: metrics.size.width / 2 ,y: metrics.size.height * (CGFloat(10) / CGFloat(11)))
}
}
.cornerRadius(7.5)
.frame(minWidth: 0, maxWidth: 15)
I've solved my solution. I managed to create the same view by making a custom ProgressView style.

How to detect whether the scrollview was scrolled or not in SwiftUI?

I have a horizontal ScrollView and would like to provide a function that when the first element is not visible anymore an arrow should appear to signalize that there are some other elements.
I've tried the DragGesture() to read the translation.width but it seems to be buggy in combination with the ScrollView.
So I'm looking for a way to detect whether the ScrollView was scrolled and how far. Is there any way?
private let gridItems = [GridItem(.flexible())]
ScrollView(.horizontal, showsIndicators: false) {
HStack{
LazyHGrid(rows: gridItems){
ForEach(viewModel.imageOptions, id: \.self){ image in
ZStack {
Image(image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 60, height: 60)
.background(Color.white)
.clipShape(Circle())
.background(
Circle()
.clipShape(Circle())
.shadow(color: Color.black.opacity(0.4), radius: 3, x: 5.0, y: 5.0)
)
.onTapGesture {
print("selected")
}
.padding(15)
Circle()
.strokeBorder(Color.orange, lineWidth: 5)
.frame(width: 70, height: 70)
}
}
}
}
}
}

Force SwiftUI view to overflow in trailing direction instead of expanding in both directions

I have a SwiftUI view in an HStack that will be wider than it's parent. When this happens, the HStack expands in both leading and trailing directions, despite being put in a VStack with alignment set to .leading (which I had hoped would anchor the leading edge).
Consider the following simplified Playground code:
import SwiftUI
import PlaygroundSupport
struct TestView : View {
var body: some View {
VStack {
VStack(alignment: .leading) {
HStack {
Rectangle()
.foregroundColor(Color.red)
.frame(width: 20)
Rectangle()
.foregroundColor(.green)
}.frame(height: 40)
HStack {
Rectangle()
.foregroundColor(.orange)
.frame(width: 10)
Rectangle()
.foregroundColor(.green)
.frame(width: 200)
Rectangle()
.foregroundColor(.yellow)
.frame(width: 10)
}
.frame(height: 40)
HStack {
Rectangle()
.foregroundColor(.orange)
.frame(width: 10)
Rectangle()
.foregroundColor(.green)
.frame(width: 200)
Rectangle()
.foregroundColor(.yellow)
.frame(width: 10)
}
.frame(height: 40)
}
.border(Color.red)
}
.frame(width: 300, height: 400)
.border(Color.black)
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = UIHostingController(rootView: TestView())
In the above example, everything fits within the view and behaves as expected:
However, change the last green Rectangle to a width of 500 instead of 200, and the following happens:
You can see that the orange leading Rectangle has disappeared, pushed off to the left. Similarly, the yellow Rectangle at the end has been pushed to the right.
What I would like to have happen is have the orange Rectangle visible, meaning that all 3 HStacks would have the same x origin (0). So, the result would be seeing the orange Rectangle on the left, the green visible, but spilling over to the right, and the yellow invisible, since it would be pushed off the screen.
I assume this may have to do with using clipped() or fixedSize but I haven't been able to find a working solution yet.
The easiest solution (no workarounds or other views required) is to set the alignment in the frame modifier of the outer VStack to .leading like this:
struct TestView : View {
var body: some View {
VStack {
VStack(alignment: .leading) {
HStack {
Rectangle()
.foregroundColor(Color.red)
.frame(width: 20)
Rectangle()
.foregroundColor(.green)
}.frame(height: 40)
HStack {
Rectangle()
.foregroundColor(.orange)
.frame(width: 10)
Rectangle()
.foregroundColor(.green)
.frame(width: 200)
Rectangle()
.foregroundColor(.yellow)
.frame(width: 10)
}
.frame(height: 40)
HStack {
Rectangle()
.foregroundColor(.orange)
.frame(width: 10)
Rectangle()
.foregroundColor(.green)
.frame(width: 500)
Rectangle()
.foregroundColor(.yellow)
.frame(width: 10)
}
.frame(height: 40)
}
.border(Color.red)
}
.frame(width: 300, height: 400, alignment: .leading) //<= here
.border(Color.black)
}
}
struct TestView : View {
var body: some View {
VStack {
VStack(alignment: .leading) {
HStack {
Rectangle()
.foregroundColor(Color.red)
.frame(width: 20)
Rectangle()
.foregroundColor(.green)
}.frame(height: 40)
HStack {
Rectangle()
.foregroundColor(.orange)
.frame(width: 10)
Rectangle()
.foregroundColor(.green)
.frame(width: 200)
Rectangle()
.foregroundColor(.yellow)
.frame(width: 10)
}
.frame(height: 40)
HStack {
GeometryReader{_ in
Rectangle()
.foregroundColor(.orange)
.frame(width: 10)
.offset(x: 0)
Rectangle()
.foregroundColor(.green)
.frame(width: 500)
.offset(x: 10)
Rectangle()
.foregroundColor(.yellow)
.frame(width: 10)
.offset(x: 510)
}
}
.frame(height: 40)
}
.border(Color.red)
}
.frame(width: 300, height: 400)
.border(Color.black)
}
}