OK. I am working on reproducing this project as a pure SwiftUI View.
I have my work cut out for me. I know that the system is still pretty buggy. The first update helped a lot, but there's still issues.
Nevertheless, I still want most of the problems to be of my own making. Those, I can fix.
I am laying out the control in the same manner as with the original, with three layers: a background circle, a middle movable element (the spinner), and a top transparency mask.
The circle is done, and I'm working on mapping out the spinner layer.
I want it to look something like this:
I currently have it looking like this:
The issue is that I am having a devil of a time getting the image dimensions in order to inform the offset, but what I'd really like, is to simply make the image anchor point the top, so the offset is sorted.
I tried messing with the alignmentGuide and frame stuff, but I must not be understanding it well, as they seem to be ignored.
Here's the code snippet for one image radii, which is where this should happen:
struct RVS_SwiftUISpinner_ItemDisplayView: View {
#State var itemImage: Image
#State var size: CGSize
var body: some View {
return VStack(alignment: .center, spacing: 0) {
self.itemImage
.resizable()
.aspectRatio(contentMode: .fit)
.offset(y: -self.size.height)
.frame(alignment: .top)
}
.frame(width: self.size.width,
height: self.size.height,
alignment: .bottom)
.background(Color(red: 0, green: 1.0, blue: 1.0, opacity: 0.25))
.offset(y: -self.size.height / 2.0)
}
}
Does anyone see what I'm doing wrong? I will keep at this, and I WILL find and fix the issue eventually, but I'm still basically making this up as I go along.
OK. I think I have it:
struct RVS_SwiftUISpinner_ItemDisplayView: View {
#State var itemImage: Image
#State var size: CGSize
var body: some View {
return VStack(alignment: .center, spacing: 0) {
self.itemImage
.resizable()
.aspectRatio(contentMode: .fit)
.frame(alignment: .top)
}
.frame(width: self.size.width,
height: self.size.height,
alignment: .top)
.background(Color(red: 0, green: 1.0, blue: 1.0, opacity: 0.25))
.offset(y: -self.size.height / 2.0)
}
}
I made the OUTER alignment .top.
Onward and upward...
Related
I have a rectangle in a frame as shown below
Rectangle in iPhone 13
Rectangle in iPhone 8
I am trying to proportionally adjust the height of rectangle so that there is an equal proportion of white in every iPhone model, but as is clearly visible the rectangle takes over the entire screen in a smaller iPhone model.
I am sure SwiftUI has a formal feature that allows for the rectangle to be resized proportionally.
How can I do this? Code shown below
VStack {
ZStack {
//Rectangle 12
Rectangle()
.fill(Color(#colorLiteral(red: 0.7137255072593689, green: 0.10196077823638916, blue: 0.10196077823638916, alpha: 1)))
.frame(width: 391, height: 800)
}
Spacer()
}.ignoresSafeArea(edges: .top)
EDIT: It seems like my question, while describing what I want in isolation did not truly encapsulate what I want
I want to create this design
enter image description here
However, I want it to scale properly across devices.
How would I go about doing this?
This is exactly what a GeometryReader is for. It reads the size given to it, in this case the whole screen. Then, you can put a .frame() on the Spacer which is a compressible view to keep it at a set size. In the example code below, it is 10% of the view height.
struct ProportionalRectangle: View {
var body: some View {
GeometryReader { geometry in
VStack {
Rectangle()
.fill(Color(#colorLiteral(red: 0.7137255072593689, green: 0.10196077823638916, blue: 0.10196077823638916, alpha: 1)))
Spacer()
.frame(height: geometry.size.height * 0.1)
}
}
.ignoresSafeArea()
}
}
Do you mean you want to divide the view into red rectangle and white.
struct ContentView: View {
var body: some View {
VStack {
Rectangle()
.fill(.red)
.frame(maxWidth: .infinity, maxHeight: .infinity)
Rectangle()
.fill(.white)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}.ignoresSafeArea()
}
}
I am trying to render a linear gradient on a rectangle. Here is the code:
Rectangle()
.foregroundColor(Color.red)
.overlay(
LinearGradient(gradient: Gradient(colors: [.red, .yellow, .blue]), startPoint: .topLeading, endPoint: .bottomTrailing)
)
.frame(width: 300, height: 200)
.position(x: 170, y: 120)
When I render it on square, everything looks correct:
When I render it on a rectangle, however, it stops looking like it's going from topLeading corner to bottomTrailing. It just looks like it's the same one which was clipped:
Here is how it's rendered in svg (blue is not the same it seems, but that's not the important part), and how I want it to look in swift:
The yellow diagonal should go directly from one corner to another, since I am specifying startPoint: .topLeading, endPoint: .bottomTrailing. It says here SwiftUI diagonal LinearGradient in a rectangle that this is a standard behaviour, and that's ok - I'm not trying to say SwiftUI renders it incorrectly, I just need a way to make it look like in svg.
What a fun problem :)
The "naive" approach would be to render the gradient as a square and then either rotate it or squeeze or stretch it to fit the rectangle. Rotating requires more maths so here's the "squeezing" version:
struct ContentView: View {
var body: some View {
Rectangle()
.overlay(
GeometryReader { g in
LinearGradient(
gradient: Gradient(colors: [.red, .yellow, .blue]),
startPoint: .topLeading,
endPoint: .bottomTrailing
)
.frame(width: g.size.width, height: g.size.width)
.scaleEffect(x: 1.0, y: g.size.height / g.size.width, anchor: .top)
}
)
.frame(width: 300, height: 200)
}
}
Notes:
To do the squeeze, we need to know the proportions of the rectangle, you were already using an overlay() and overlays are bounded by the Rectangle() in this case, so we can just read the size with the GeometryReader();
We need to start with the gradient applied to a square and there is a couple of ways to do squares. As we already know the size of the rectangle, I went with a square with a side equal to the width of the enclosing rectangle;
Then we apply the scaleEffect(), squeezing the height of the square to the height of the rectangle.
Please let me know if that's what you were looking for or if there is a better way to do to this!
Cheers,
–Baglan
I have a RoundedRectangle, and my objective is to lay another RoundedRectangle over it such that the overlay shows a "percentage complete". I can't seem to find the proper incantation to do so, though.
I think that ideally, the overlay should somehow only show a percentage of itself. Resizing itself to match the percentage skews the shape of the overlay.
import PlaygroundSupport
struct ContentView: View {
#State private var value: Double = 0
var body: some View {
GeometryReader { geom in
VStack {
Slider(value: self.$value, in: 0...1, step: 0.01)
ZStack {
ZStack(alignment: .leading) {
// The main rectangle
RoundedRectangle(cornerRadius: 10)
.fill(Color.blue)
.frame(width: geom.size.width,
height: 200)
// The progress indicator...
RoundedRectangle(cornerRadius: 10)
.fill(Color.red)
.opacity(0.5)
.frame(width: CGFloat(self.value) * geom.size.width,
height: 200)
}
Text("\(Int(self.value * 100)) %")
}
}
}
.padding()
}
}
PlaygroundPage.current.setLiveView(ContentView())
In the above playground, if you look at 1, 2, or even 3 %, you can see that the red overlay is out of the blue background rectangle bounds in the upper and lower left. See image below.
I feel like this is not the proper solution, but I also couldn't find the right mix of things (trying scaleEffect, a bunch of offset and position math) to really nail it.
To me, like I said above, it feels like the overlay should be able to say "only make my left-most 40% visible" when the value is 0.4.
That was long-winded, I apologize for that. If you've read this far, and have any advice to impart, I'd be incredibly appreciative.
Thanks!
If I correctly understood your concern, here is a solution. Tested with Xcode 11.4 / iOS 13.4.
ZStack {
ZStack(alignment: .leading) {
// The main rectangle
RoundedRectangle(cornerRadius: 10)
.fill(Color.blue)
.frame(width: geom.size.width,
height: 200)
// The progress indicator...
RoundedRectangle(cornerRadius: 10)
.fill(Color.red)
.opacity(0.5)
.frame(width: CGFloat(self.value) * geom.size.width,
height: 200)
}
.clipShape(RoundedRectangle(cornerRadius: 10)) // << here !!
This how I approach it so I don't have to manage more than one cornerRadius.
VStack {
ZStack(alignment: .leading) {
// The main rectangle
Rectangle()
.fill(Color.blue)
.frame(width: geom.size.width,
height: 200)
// The progress indicator...
Rectangle()
.fill(Color.red)
.opacity(0.5)
.frame(width: CGFloat(self.value) * geom.size.width,
height: 200)
}
.cornerRadius(10)
So I just started developing with SwiftUI and I'm running in a small problem. Subviews are also displaying superview's shadow, even if the superview has a background. Does someone know how to fix this?
HStack {
HStack {
[...]
}
.padding(.leading, 12.0)
.padding(.trailing, 4.0)
.padding(.vertical, 16.0)
.background(Color("lightGreen"))
.cornerRadius(10)
}
.padding(8)
.background(Color.white)
.shadow(color: Color("tabShadow"), radius: 0.0, x: 0.0, y: -0.5)
.shadow(color: Color("tabShadow"), radius: 0.0, x: 0.0, y: 0.5)
As stated, the first HStack's shadow shouldn't be replicated into the child one, but it is. Only the first one though. Any hints?
Certain modifiers, when placed on a stack, are inherited by all their children. For instance, if you have a stack containing a bunch of Text views, you can place one .font() modifier on the stack and they will all be modified.
It appears that .shadow() is one of those modifiers. As to why only one is inherited, I suspect that the designers of SwiftUI don't expect .shadow() to be called more than once on a particular view, and didn't test for that.
If you are just trying to get a colored line across the top and bottom of the view, maybe try something like
.background(Color.white)
.background(Color("tabShadow").offset(x: 0, y: -0.5))
.background(Color("tabShadow").offset(x: 0, y: 0.5))
I am also newbie in swiftUI , but I think the problem is related to modifiers order and the fact that they change the View type.
I was able to solve the problem by adding .background(Color.white) and .cornerRadius(2.0) modifiers just before the shadow modifier and that applied the changes in parent (not children) View.
struct TestSwiftUIView: View {
var body: some View {
VStack {
Text("Hello World")
Text("Hello World")
}
.padding()
.background(Color.white)
.cornerRadius(2.0)
.shadow(radius: 3)
}
}
You can try overlay and background tricks when you have to make them render in multiple passes. In above case, the overlay will not be affected by the shadow or other effects.
If you think they are subViews, actually, they just render after the superView. It's a 2D world. So the overlay will be quite independently.
The only problem is just the size of overlay.
The hidden() is used here to occupy the position of invisible overlay. It's very cool if you master these layout skills.
struct ContentView: View {
var body: some View {
HStack {
SubContentView().hidden()
}
.padding(8)
.background(Color.white)
.shadow(color: Color.red, radius: 0, x: 0.0, y: -0.5)
.shadow(color: Color.red, radius: 0, x: 0.0, y: 0.5)
.overlay(SubContentView())
}
}
struct SubContentView: View {
var body: some View {
HStack{
Text("a")
Text("b")
Text("c")
Text("a")
Text("b")
Text("c")
}.padding(.leading, 12.0)
.padding(.trailing, 4.0)
.padding(.vertical, 16.0)
.background(Color.green)
.cornerRadius(10)
}
}
If you want to prevent the shadow the be applied to subviews then use the shadow modifier only inside the .background modifier like so:
VStack {
...
}.background(Color.white.shadow(color: .black.opacity(0.3), radius: 1, x: 1, y: 1))
Just Wrap your main view with some View and setShadow(1) to that view. and setShadow(0) to your main view. it overrides the parents shadow.
Lets say you have:
VStack{
Text("1")
Text("2")
}
and you want to set shadow to this VStack. So, wrap this VStack with another VStack (any View), and setShadow to that stack. Inner shadow overrides outer shadow. Finally your code should be:
VStack{
VStack{
Text("1")
Text("2")
}.shadow(radius: 0)
}
.shadow(radius: 1)
I am unable to find any related documentation on how to do a linear gradient on the foreground for an image I have with SwiftUI.
I have tried to do it like so:
Image("IconLoseWeight")
.frame(width: 30.0, height: 30.0)
.padding(.leading, 17)
.foregroundColor(LinearGradient(gradient: Gradient(colors: [.white, .black]), startPoint: .top, endPoint: .bottom))
Actually, the code shown above doesn't display any errors, but it breaks the code with warnings that make no sense in the top level Stacks (which I think is a bug with Xcode or SwiftUI). If I remove the foreground modifier, the code runs perfectly.
That's because foregroundColor wants a Color, but LinearGradient is a struct that conforms to the protocols ShapeStyle and View.
If I understand you correctly you want to fill the intransparent area of an image with a gradient?
ZStack {
Color.white // For the background. If you don't need a background, you don't need the ZStack.
LinearGradient(gradient: Gradient(colors: [.green, .blue]), startPoint: .top, endPoint: .bottom)
.mask(Image("AssetWithTransparency")
.resizable()
.padding()
.aspectRatio(contentMode: .fit))
}.cornerRadius(15)
The result looks like this:
The task here is to display gradient over an image. To display one view over another SwiftUI provides ZStack view, so, the code can have the next structure:
ZStack {
<Image>
<Rectangle with gradient>
}
Additionally, to make sure the image we use is resized correctly to the specified frame resizable modifier should be applied with correct contentMode:
Image("IconLoseWeight")
.resizable() // Make it resizable
.aspectRatio(contentMode: .fit) // Specifying the resizing mode so that image scaled correctly
After all, we need to apply frame and padding parameter to ZStack so that gradient has the same size as the image.
The result would look like that:
ZStack {
Image("IconLoseWeight")
.resizable() // Making the image resizable to the container size
.aspectRatio(contentMode: .fit) // Setting up resizing mode so that the image scaled correctly
Rectangle() // Shapes are resizable by default
.foregroundColor(.clear) // Making rectangle transparent
.background(LinearGradient(gradient: Gradient(colors: [.clear, .black]), startPoint: .top, endPoint: .bottom), cornerRadius: 0)
// Specifying gradient (note that one color is .clear)
}
.frame(width: 30, height: 30) // Applying frame
.padding(.leading, 17) // Applying padding
Note, that we use a gradient from .clear to .black as we need a transparent gradient to make the image visible.
Agree with #RyuX51's answer and it's working well. But some how size and alignment of my image got changed. Because LinearGradient's frame isn't set. So here i came up with the solution for just applying gradient to the Image,
VStack{
Spacer()
Button(action: {
print("Add Photos")
}, label: {
LinearGradient(gradient: Gradient(colors: [.green, .blue]), startPoint: .top, endPoint: .bottom)
.mask(Image(systemName: "plus.circle.fill")
.resizable()
.aspectRatio(contentMode: .fit)
).frame(width: 70, height: 70, alignment: .center)
})
}
The best way to do this as of the most recent SwiftUI release would be to use the .foregroundStyle() view modifier. I'm not sure when this approach became available but this code was tested with Xcode 14 and iOS 16.
Sample code below:
let gradient = Gradient(colors: [.purple, .cyan, .orange])
var body: some View {
Image(systemName: "figure.strengthtraining.traditional")
.font(.title)
.foregroundStyle(.linearGradient(gradient, startPoint: .top, endPoint: .bottom))
}
RyuX51's answer worked for me except the view ended up stretching to fill the available space. I wanted it to shrink to fit the size of the image I was using.
This worked well for my needs:
extension View {
/// Fills in self with `fill`, maintaining self's natural size
/// - Parameter fill: View to fill in self with (i.e., a gradient)
/// - Returns: Filled-in version of self
#ViewBuilder func filled(with fill: () -> some View) -> some View {
self.overlay {
fill().mask { self }
}
}
}
So for example:
Image(systemName: "car.2.fill")
.font(.system(size: 75))
.imageScale(.large)
.aspectRatio(contentMode: .fit)
.filled {
LinearGradient(
gradient: Gradient(colors: [.green, .blue]), startPoint: .top, endPoint: .bottom)
}
.border(.orange, width: 2)
will give you: