hey everyone, I designed some stuff in figma and now that Im trying to transfer everything to swiftui I'm having some troubles. Does anyone know how I can make this ellipse shaped gradient in swiftui? I tried the swuiftui inspector but it just gave me a circular shape. Thanks for the help.
iOS15 only :
struct ContentView: View {
var body: some View {
Rectangle()
.fill(
.ellipticalGradient(
colors: [.red, .orange, .yellow],
center: .center,
startRadiusFraction: 0.2,
endRadiusFraction: 0.6
)
)
}
}
or :
struct ContentView2: View {
let gradient = Gradient(stops:
[.init(color: .red, location: 0.2),
.init(color: .orange, location: 0.6),
.init(color: .yellow, location: 0.8)])
var body: some View {
Rectangle()
.fill(
.elliptical(gradient,
center: .center,
startRadiusFraction: 0.1,
endRadiusFraction: 0.6)
)
}
}
You can use RadialGradient by applying scaleEffect modifier to shrink it:
struct ContentView: View {
let gradient = Gradient(
stops: [
.init(color: .green.opacity(0.4), location: 0),
.init(color: .green.opacity(0.5), location: 0.7),
.init(color: .green.opacity(0), location: 1),
]
)
var body: some View {
GeometryReader { geometry in
Rectangle()
.fill(
RadialGradient(
gradient: gradient,
center: .center,
startRadius: 0,
endRadius: geometry.size.width / 2
)
)
.scaleEffect(x: 1, y: geometry.size.height / geometry.size.width, anchor: .center)
}
}
}
Since iOS 15 you can get rid of both GeometryReader and scaleEffect using EllipticalGradient, so it may be more performant, or at least less code:
struct ContentView: View {
let gradient = Gradient(
stops: [
.init(color: .green.opacity(0.4), location: 0),
.init(color: .green.opacity(0.5), location: 0.7),
.init(color: .green.opacity(0), location: 1),
]
)
var body: some View {
Rectangle()
.fill(
EllipticalGradient(
gradient: gradient,
center: .center
)
)
}
}
Both variants looks totally the same:
Related
Let's say I have a LinearGradient of 3 colors Red, Blue, and Green.
Can we possibly make the soft gradient between these 3 colors removed? I am trying to animate between hard and soft gradient of LinearGradient.
Something like this:
LinearGradient(colors: [.red, .blue, .green], startPoint: .trailing, endPoint: .leading)
what i have:
what i want it to be when some condition is met:
You can use LinearGradient(stops: ) init. It will not give you a full hard edge, but quite close. You can then change the stop values or the array for animation.
struct ContentView: View {
let stops = [Gradient.Stop(color: Color.green, location: 0.0),
Gradient.Stop(color: Color.green, location: 0.33),
Gradient.Stop(color: Color.blue, location: 0.33),
Gradient.Stop(color: Color.blue, location: 0.66),
Gradient.Stop(color: Color.red, location: 0.66),
Gradient.Stop(color: Color.red, location: 1.0)]
var body: some View {
Rectangle()
.fill(
LinearGradient(stops: stops, startPoint: .trailing, endPoint: .leading)
)
.frame(height: 200)
.padding()
}
}
There is an other way for getting same result with less and more easier way, like this:
struct ContentView: View {
var body: some View {
HorizontalQuantumLinearGradientView(colors: [.red, .blue, .green])
}
}
struct HorizontalQuantumLinearGradientView: View {
let colors: [Color]
var body: some View {
HStack(spacing: .zero) {
ForEach(colors, id:\.self) { item in item }
}
}
}
struct VerticalQuantumLinearGradientView: View {
let colors: [Color]
var body: some View {
VStack(spacing: .zero) {
ForEach(colors, id:\.self) { item in item }
}
}
}
I have what I thought was a simple task, but it appears otherwise. As part of my code, I am simply displaying some circles and when the user clicks on one, it will navigate to a new view depending on which circle was pressed.
If I hard code the views in the NavigationLink, it works fine, but I want to use a dynamic array. In the following code, it doesn't matter which circle is pressed, it will always display the id of the last item in the array; ie it passes the last defined dot.destinationId to the Game view
struct Dot: Identifiable {
let id = UUID()
let x: CGFloat
let y: CGFloat
let color: Color
let radius: CGFloat
let destinationId: Int
}
struct ContentView: View {
var dots = [
Dot(x:10.0,y:10.0, color: Color.green, radius: 12, destinationId: 1),
Dot(x:110.0,y:10.0, color: Color.green, radius: 12, destinationId: 2),
Dot(x:110.0,y:110.0, color: Color.blue, radius: 12, destinationId: 3),
Dot(x:210.0,y:110.0, color: Color.blue, radius: 12, destinationId: 4),
Dot(x:310.0,y:110.0, color: Color.red, radius: 14, destinationId: 5),
Dot(x:210.0,y:210.0, color: Color.blue, radius: 12, destinationId: 6)]
var lineWidth: CGFloat = 1
var body: some View {
NavigationView {
ZStack
Group {
ForEach(dots){ dot in
NavigationLink(destination: Game(id: dot.destinationId))
{
Circle()
.fill(dot.color)
.frame(width: dot.radius, height: dot.radius)
.position(x:dot.x, y: dot.y )
}
}
}
.frame(width: .infinity, height: .infinity, alignment: .center )
}
.navigationBarTitle("")
.navigationBarTitleDisplayMode(.inline)
}
}
struct Game: View {
var id: Int
var body: some View {
Text("\(id)")
}
}
I tried to send the actual view as i want to show different views and used AnyView, but the same occurred It was always causing the last defined view to be navigated to.
I'm really not sure what I a doing wrong, any pointers would be greatly appreciated
While it might seem that you're clicking on different dots, the reality is that you have a ZStack with overlapping views and you're always clicking on the top-most view. You can see when you tap that the dot with ID 6 is always the one actually getting pressed (notice its opacity changes when you click).
To fix this, move your frame and position modifiers outside of the NavigationLink so that they affect the link and the circle contained in it, instead of just the inner view.
Also, I modified your last frame since there were warnings about an invalid frame size (note the different between width/maxWidth and height/maxHeight when specifying .infinite).
struct ContentView: View {
var dots = [
Dot(x:10.0,y:10.0, color: Color.green, radius: 12, destinationId: 1),
Dot(x:110.0,y:10.0, color: Color.green, radius: 12, destinationId: 2),
Dot(x:110.0,y:110.0, color: Color.blue, radius: 12, destinationId: 3),
Dot(x:210.0,y:110.0, color: Color.blue, radius: 12, destinationId: 4),
Dot(x:310.0,y:110.0, color: Color.red, radius: 14, destinationId: 5),
Dot(x:210.0,y:210.0, color: Color.blue, radius: 12, destinationId: 6)]
var lineWidth: CGFloat = 1
var body: some View {
NavigationView {
ZStack {
Group {
ForEach(dots){ dot in
NavigationLink(destination: Game(id: dot.destinationId))
{
Circle()
.fill(dot.color)
}
.frame(width: dot.radius, height: dot.radius) //<-- Here
.position(x:dot.x, y: dot.y ) //<-- Here
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center )
}
.navigationBarTitle("")
.navigationBarTitleDisplayMode(.inline)
}
}
}
Basically what Im trying to achieve is this:
To fade an image out from the top to bottom. I tried to do it with overlays but it simply does not look good
Here is a working view to fade an image out from the top to the bottom.
Lmk if it works!
Source Code
struct ContentView: View {
var body: some View {
VStack {
Image("Explore")//Your Image
.resizable()
}
//We can use the LinearGradient in the mask modifier to fade it top to bottom
.mask(LinearGradient(gradient: Gradient(stops: [
.init(color: .black, location: 0),
.init(color: .clear, location: 1),
.init(color: .black, location: 1),
.init(color: .clear, location: 1)
]), startPoint: .top, endPoint: .bottom))
.padding()
.frame(width: 400, height: 400)
}
}
Preview
How is this problem different from other animation questions: I have not seen any articles addressing reversed Text. Please see below:
Here is the code that causes this:
.rotation3DEffect(
.init(degrees: isTapped ? 180 : 0),
axis: (x: 0.0, y: 1.0, z: 0.0),
anchor: .center,
anchorZ: 20.0,
perspective: 1.0
)
Here is how I tried to resolve: :
Tried to replace 180º with 360º which of course made the card spin twice (undesired but rendered the text straight)
Tried to use .reversed() as a workaround
Played with all of the params of 3DEffect to no avail
Searched internet for solution, no answer to this specific problem
Edit Also if anyone knows why the text renders like that when going back to arabic view.. that would be great additional assistance.
Edit Adding example code, able to just copy and paste and go
Edit Another edit, forgot I tried to use .reversed() as a workaround
import SwiftUI
struct ContentView: View {
let grid = [GridItem(.fixed(100), spacing: 5, alignment: .center),
GridItem(.fixed(100), spacing: 5, alignment: .center),
GridItem(.fixed(100), spacing: 5, alignment: .center)]
var body: some View {
VStack {
LazyVGrid(columns: grid, content: {
ForEach(Example.example, id: \.name) { ex in
AnimatedView(data: ex)
}
})
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct Example {
let name: String
let translation: String
static let example =
[Example(name: "First", translation: "Second"),
Example(name: "Third", translation: "Fourth"),
Example(name: "Fifth", translation: "Sixth"),
Example(name: "Seventh", translation: "Eigth"),
Example(name: "Ninth", translation: "Tenth"),
Example(name: "Eleventh", translation: "Twelth")
]
}
struct AnimatedView: View {
#State private var isTapped = false
let data: Example
var body: some View {
Button(action: {self.isTapped.toggle()}) {
Text(isTapped ? data.name : data.translation)
.padding()
.foregroundColor(.white)
.frame(width: 100)
.background(isTapped ? Color.blue : .black)
.cornerRadius(8)
}.rotation3DEffect(
.init(degrees: isTapped ? 180 : 0),
axis: (x: 0.0, y: 1.0, z: 0.0),
anchor: .center,
anchorZ: 0,
perspective: 0
)
.animation(.linear)
}
}
struct AnimatedView: View {
#State private var isTapped = false
let data: Example
var body: some View {
Button(action: {self.isTapped.toggle()}) {
Text(isTapped ? data.name : data.translation)
.padding()
.foregroundColor(.white)
.frame(width: 100)
.background(isTapped ? Color.blue : .black)
.cornerRadius(8)
.rotation3DEffect( //<- here
.init(degrees: isTapped ? 180 : 0),
axis: (x: 0.0, y: 1.0, z: 0.0),
anchor: .center,
anchorZ: 0,
perspective: 0
) }.rotation3DEffect(
.init(degrees: isTapped ? 180 : 0),
axis: (x: 0.0, y: 1.0, z: 0.0),
anchor: .center,
anchorZ: 0,
perspective: 0
)
.animation(.linear)
}
}
Having rectangle with gradient fill and trying to animate color change.
...
Rectangle()
.fill(LinearGradient(
gradient: .init(stops: [Gradient.Stop(color: myColor, location: 0.0), Gradient.Stop(color: .blue, location: 1.0)]),
startPoint: .init(x: 0.5, y: startPoint),
endPoint: .init(x: 0.5, y: 1-startPoint)
))
.animation(Animation.easeIn(duration: 2))
...
While change of starting points is animated nicely; the color change is not animated at all and just "switches"
Any hints?
So if anyone will be struggling with that my current solution is having two gradients in ZStack and animating opacity of the top one
Example gif
Here is rough example, the code sure can be written more nicely:
///helper extension to get some random Color
extension Color {
static func random()->Color {
let r = Double.random(in: 0 ... 1)
let g = Double.random(in: 0 ... 1)
let b = Double.random(in: 0 ... 1)
return Color(red: r, green: g, blue: b)
}
}
struct AnimatableGradientView: View {
#State private var gradientA: [Color] = [.white, .red]
#State private var gradientB: [Color] = [.white, .blue]
#State private var firstPlane: Bool = true
func setGradient(gradient: [Color]) {
if firstPlane {
gradientB = gradient
}
else {
gradientA = gradient
}
firstPlane = !firstPlane
}
var body: some View {
ZStack {
Rectangle()
.fill(LinearGradient(gradient: Gradient(colors: self.gradientA), startPoint: UnitPoint(x: 0, y: 0), endPoint: UnitPoint(x: 1, y: 1)))
Rectangle()
.fill(LinearGradient(gradient: Gradient(colors: self.gradientB), startPoint: UnitPoint(x: 0, y: 0), endPoint: UnitPoint(x: 1, y: 1)))
.opacity(self.firstPlane ? 0 : 1)
///this button just demonstrates the solution
Button(action:{
withAnimation(.spring()) {
self.setGradient(gradient: [Color.random(), Color.random()])
}
})
{
Text("Change gradient")
}
}
}
}
Update: *In the end I have explored several ways of animating gradient fills and summarized them here: https://izakpavel.github.io/development/2019/09/30/animating-gradients-swiftui.html *
Wanted to share one more simple option.
Two rectangles with animated solid colors on top of each other.
The top ZStack uses a gradient mask to blend them together.
struct ContentView: View {
#State var switchColor = false
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 30)
.fill(switchColor ? Color.purple : Color.green)
ZStack {
RoundedRectangle(cornerRadius: 30)
.fill(switchColor ? Color.pink : Color.yellow)
}
.mask(LinearGradient(gradient: Gradient(colors: [.clear,.black]), startPoint: .top, endPoint: .bottom))
}
.padding()
.onTapGesture {
withAnimation(.spring()) {
switchColor.toggle()
}
}
}
}