It seems there is a potential bug in SwiftUI. I am trying to put a rectangle with opacity 0.5 on top of an image.
When I try to fix the transparent rectangle on top, from 100px width, it goes down instead of sticking to the top.
Here is the code:
ZStack {
VStack {
Image("movistar")
.resizable(capInsets: EdgeInsets(), resizingMode: .stretch)
.scaledToFit()
.cornerRadius(8)
.padding(15)
.frame(minWidth: Global.SCREEN_WIDTH)
}
VStack {
HStack {
Rectangle()
.fill(Color(red: 0, green: 0, blue: 0, opacity: 0.5))
.frame(width: 110, height: Global.SCREEN_WIDTH / 4)
}
Spacer()
}
.scaledToFit()
.cornerRadius(8)
.padding(15)
.frame(width: Global.SCREEN_WIDTH, height: Global.SCREEN_WIDTH)
There is no bug here. If you add a .background to all of your layers, you will see that because of the way you set up the view (ie. Spacer, scaledToFit, etc.) the actual frames of the views are not necessarily the edges of the image. You also have not set the alignment of any of the Stacks or Frames.
There are many ways to do what you are trying to do, but I believe this is the simplest:
var body: some View {
Image("movistar")
.resizable(capInsets: EdgeInsets(), resizingMode: .stretch)
.scaledToFit()
.cornerRadius(8)
.frame(minWidth: UIScreen.main.bounds.width)
.overlay(
Rectangle()
.fill(Color(red: 0, green: 0, blue: 0, opacity: 0.5))
.frame(width: 110, height: UIScreen.main.bounds.width / 4)
, alignment: .top
)
}
Finally got into a solution: .scaleToFit() was messing with the VStack(). After deleting, it worked perfectly. I also got rid of the HStack().
Related
I just can't seem to get rid of the ~6 height padding(?) at the top of ScrollView. I've tried setting padding to 0 on both the ScrollView itself and on it's content. I've tried using SwiftUI-Introspect to get at the content insets, but they appear to already be 0 for the top. I could just use offset(y: -6), but I'd like to understand what is causing the padding. Does anyone know?
var body: some View {
VStack(spacing: 0) {
Rectangle()
.frame(width: UIScreen.main.bounds.width, height: 200)
.foregroundColor(.blue)
Rectangle()
.frame(width: UIScreen.main.bounds.width, height: 200)
.foregroundColor(.red)
ScrollView {
Rectangle()
.frame(width: UIScreen.main.bounds.width, height: 200)
.foregroundColor(.black)
}
}
}
Let's say I have a ZStack with an indeterminate/flexible width. There are four views within this ZStack. How would I distribute the views evenly... as if I were fanning out a deck of cards (with each card overlapping the next), like this:
I can accomplish this effect with .offset(x: ?) if I know the width of the container:
ZStack {
Text("One")
.frame(width: 99, height: 150, alignment: .topLeading)
.background(Color.blue)
.offset(x: -99)
Text("Two")
.frame(width: 99, height: 150, alignment: .topLeading)
.background(Color.red)
.offset(x: -33)
Text("Three")
.frame(width: 99, height: 150, alignment: .topLeading)
.background(Color.orange)
.offset(x: 33)
Text("Four")
.frame(width: 99, height: 150, alignment: .topLeading)
.background(Color.green)
.offset(x: 99)
}
But what if I don't know the width of the container? Or what if it changes? Or what if the number of views within the container changes?
Basically, I just need to distribute views within the ZStack evenly... no matter what the width is. How?
Since you have that many cards, you should use a ForEach. This will make adding/removing cards easier and saves some code.
But what if I don't know the width of the container?
This is where GeometryReader comes in! You can use it to get the width of the container.
I just need to distribute views within the ZStack evenly
Once you get the width from the GeometryReader, you can just divide by the number of elements in the cards array to calculate each card's width.
struct Card {
var name: String
var color: Color
}
struct ContentView: View {
let cards = [
Card(name: "One", color: .blue),
Card(name: "Two", color: .red),
Card(name: "Three", color: .orange),
Card(name: "Four", color: .green)
]
var body: some View {
ZStack {
GeometryReader { proxy in
ForEach(cards.indices, id: \.self) { index in
let card = cards[index]
Text(card.name) /// add a bit of overlap
.frame(width: proxy.size.width / CGFloat(cards.count) + 10, height: 150, alignment: .topLeading)
.background(
card.color
.shadow(color: Color.black, radius: 5) /// make the overlap a bit more visible
)
.offset(x: proxy.size.width / CGFloat(cards.count) * CGFloat(index))
}
.frame(maxHeight: .infinity) /// center the cards vertically
}
}
}
}
Result:
try this:
create a function that returns x spacers (x for the number of text boxes that you want - 1) and inserts a Text view where you want it.
set the width to whatever you want it to be
have the text align to the left
replace all of the Text views in your code with this function
This should get the result you are looking for.
I have a small red Rec inside GeometryReader, if I use cornerRadius on GeometryReader it will going crop the Rec, which I do not want it. See the deference in photos:
struct ContentView: View {
var body: some View {
GeometryReader { _ in
Rectangle()
.fill(Color.red)
.frame(width: 100, height: 100, alignment: .center)
.position(x: 0, y: 0)
}
.background(Color.yellow)
.frame(width: 200, height: 300, alignment: .center)
.cornerRadius(10) // <<: Here try comment and uncomment it to see the result!
}
}
without cornerRadius:
with cornerRadius:
If you only want the yellow to have the corner radius and to not crop the red square, give the corner radius to only the background:
.background(
Color.yellow
.cornerRadius(10)
)
If you want the red square to have a corner radius as well but not be cropped, give it a corner radius as well:
Rectangle()
.fill(Color.red)
.frame(width: 100, height: 100, alignment: .center)
.cornerRadius(10)
.position(x: 0, y: 0)
Giving the GeometryReader itself a corner radius is not something that I would expect to have straightforward results, since the GeometryReader itself is not rendered per say (or at least rendered like we normally think of visual components), but as we can see from your example, it's basically treated like a container view -- giving it a corner radius ends up cropping out everything outside the bounds of it's content minus the corner radius.
In the following example, a view is placed relative to an Image, in this case in the bottom-right corner:
struct RelativePositionExampleView: View {
var body: some View {
Image("cookies")
.resizable()
.aspectRatio(contentMode: .fit)
.overlay(
GeometryReader { geometry in
Text("Hello")
.background(Color.red)
.position(x: geometry.size.width * 1.0, y: geometry.size.height * 1.0)
}
)
}
}
The "anchor point" for .position is the center of the Text view:
I tried using an alignment guide like described in Change Button Anchor Point SwiftUI, this didn't do the trick.
Is there a way to express "place based on the top/left or bottom/right corner" in such a case? (not based on Spacer - the position doesn't need to be necessarily in the corner, it could be 30% of the width f.e., 30% referring to the left edge of the positioned view).
If the goal is to just place text into image corner then there is simpler solution.
var body: some View {
Image("cookies")
.resizable()
.aspectRatio(contentMode: .fit)
.overlay(
ZStack {
Text("Hello")
}.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomTrailing)
)
}
Here is a very general way to relatively position overlay views anyway you like. Try it in Playground.
import SwiftUI
import PlaygroundSupport
extension View {
func overlay<V: View>(alignment: Alignment, relativePos: UnitPoint = .center, _ other: V) -> some View {
self
.alignmentGuide(alignment.horizontal, computeValue: { $0.width * relativePos.x })
.alignmentGuide(alignment.vertical, computeValue: { $0.height * relativePos.y })
.overlay(other, alignment: alignment)
}
}
struct ContentView: View {
var body: some View {
Circle()
.foregroundColor(.yellow)
.frame(width: 200, height: 200)
.overlay(alignment: .center, relativePos: UnitPoint(x: 0.3, y: 0.3), Capsule().fill(Color.green).frame(width: 40, height: 20))
.overlay(alignment: .center, relativePos: UnitPoint(x: 0.7, y: 0.3), Capsule().fill(Color.green).frame(width: 40, height: 20))
.overlay(alignment: .center, relativePos: UnitPoint(x: 0.5, y: 0.75), Text("Hello"))
.overlay(alignment: .center, relativePos: .bottom, Divider())
}
}
PlaygroundPage.current.setLiveView(ContentView())
I cannot figure out what compositingGroup() is. At first, I thought it is something like Merging layers in Photoshop. But it was not. Because .shadow() effects to the overlay and background views respectively even if I use .compositingGroup().
So far, I've found 2 differences when I use .compositingGroup()
Text doesn't have shadows.
The shadow size of the overlay view is slightly smaller than the above one.
What is the purpose of compositingGroup?
struct ContentView: View {
var body: some View {
VStack(spacing: 50) {
Text("Without\ncompositing")
.font(.largeTitle)
.bold()
.padding()
.foregroundColor(Color.white)
.background(RoundedRectangle(cornerRadius: 30).fill(Color.red))
.padding()
.padding()
.overlay(RoundedRectangle(cornerRadius: 30).stroke(lineWidth: 10))
.shadow(color: .blue, radius: 5)
Text("With\ncompositing")
.font(.largeTitle)
.bold()
.padding()
.foregroundColor(Color.white)
.background(RoundedRectangle(cornerRadius: 30).fill(Color.red))
.padding()
.padding()
.overlay(RoundedRectangle(cornerRadius: 30).stroke(lineWidth: 10))
.compositingGroup() // <--- I added .compositingGroup() here.
.shadow(color: .blue, radius: 5)
}
}
}
This modifier makes the following modifiers be applied to the view as a whole and not to each particular subview separately
Here's an example to better illustrate this:
struct ContentView: View {
let circles: some View = ZStack {
Circle()
.frame(width: 100, height: 100)
.foregroundColor(.red)
.offset(y: -25)
Circle()
.frame(width: 100, height: 100)
.foregroundColor(.blue)
.offset(x: -25, y: 25)
Circle()
.frame(width: 100, height: 100)
.foregroundColor(.green)
.offset(x: 25, y: 25)
}
var body: some View {
VStack(spacing: 100) {
circles
circles
.opacity(0.5)
circles
.compositingGroup()
.opacity(0.5)
}
}
}
So in your case the shadow is applied to the whole view rather than separately to the Text and overlaying RoundedRectangle
Use it when wanting to apply effects like opacity or shadow to a group of views and not each contained element by itself.
It seems like that .shadow() modifier will add both inner and outer shadow. It means that if the view is not "solid", for example, it has a "hole", .shadow() will add shadow like this:
RoundedRectangle(cornerRadius: 30)
.stroke(lineWidth: 10)
.frame(width: 300)
.shadow(color: .blue, radius: 5)
Click to see the image
So, if you do not want the inner shadow, you need to make your view be "solid", like this:
RoundedRectangle(cornerRadius: 30)
.stroke(lineWidth: 10)
.frame(width: 300)
.background(RoundedRectangle(cornerRadius: 30).fill(.white))
.shadow(color: .blue, radius: 5)
Click to see the image
However, something goes wrong again, the inner shadow doesn't disappear.
That's because I forgot to apply the .compositingGroup() modifier.
As #ramzesenok mentioned, .compositingGroup() makes the following modifiers be applied to the view as a whole and not to each particular subview separately.
So, change the code a little bit:
RoundedRectangle(cornerRadius: 30)
.stroke(lineWidth: 10)
.frame(width: 300)
.background(RoundedRectangle(cornerRadius: 30).fill(.white))
.compositingGroup()
.shadow(color: .blue, radius: 5)
Click to see the image
There is only outer shadow now.