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.
Related
I have been trying to align a view to a background image, but I haven't been able to find a solution that works for all devices. I am targeting iPhones in landscape orientation.
In this example I want to make the red rectangle align with the iMac screen. This code gets pretty close, by using an offset. It looks good in the preview canvas, but doesn't align in the Simulator or on a device.
I tried using .position(x:y:), but that was even more messy.
I found that if I crop the background so the target region is exactly centered, then it is possible, but I really hope that's not the only solution.
struct GeometryView: View {
let backgroundImageSize = CGSize(width: 1500, height: 694)
let frameSize = CGSize(width: 535, height: 304)
var body: some View {
GeometryReader { geometry in
let widthScale = geometry.size.width / backgroundImageSize.width
let heightScale = geometry.size.height / backgroundImageSize.height
let scale = widthScale > heightScale ? widthScale : heightScale
let frame = CGSize(width: frameSize.width * scale,
height: frameSize.height * scale)
ZStack {
Rectangle()
.frame(width: frame.width, height: frame.height)
.foregroundColor(.red).opacity(0.5)
.offset(x: 5, y: -8)
}
.frame(width: geometry.size.width, height: geometry.size.height)
.background(
Image("imac-on-desk")
.resizable()
.scaledToFill()
.ignoresSafeArea())
}
}
}
background image
this would work, but only on an iPhone 12. If you use .scaledToFill on the image the different display aspect ratios of phones will lead to different offsets. You could at least crop the background image , so the white screen is exactly in the center of the image.
var body: some View {
GeometryReader { geometry in
ZStack {
Image("background")
.resizable()
.scaledToFill()
.ignoresSafeArea()
Rectangle()
.foregroundColor(.red).opacity(0.5)
.frame(width: geometry.size.width / 2.45,
height: geometry.size.height / 2.1)
.offset(x: -geometry.size.width * 0.025, y: 0)
}
}
}
I try to draw a squared Image inside of a Circle to get something like that (without blue square here. It is just to show image squared border):
This code makes squared image over all circle.
ZStack() {
Circle()
.fill(.orange)
Image(systemName: "trash")
.resizable()
.foregroundColor(.blue)
.background(.orange)
}
This code makes a small image on the circle or no circle at all:
ZStack() {
Circle()
.fill(.orange)
Image(systemName: "trash")
.foregroundColor(.blue)
.background(.orange)
}
I try to find a solution to have squared image inside the circle. It should not goes outside of it.
Maybe, I would also need a small margin between image and circle's border.
Is there a way to do that easily? Or I have to use math to get circle border or something like that?
Asperi just beat me to it, but this view will just take an icon name and a radius and return a squared icon perfectly in a circle:
struct ImageOnCircle: View {
let icon: String
let radius: CGFloat
var squareSide: CGFloat {
2.0.squareRoot() * radius
}
var body: some View {
ZStack {
Circle()
.fill(.orange)
.frame(width: radius * 2, height: radius * 2)
Image(systemName: icon)
.resizable()
.aspectRatio(1.0, contentMode: .fit)
.frame(width: squareSide, height: squareSide)
.foregroundColor(.blue)
}
}
}
Use:
ImageOnCircle(icon: "trash", radius: 150)
Here is a demo of possible approach - use overlay + geometry reader to calculate internal rectangle where image is injected.
Tested with Xcode 13 / iOS 15 (blue rect is of Preview one for selected image)
Circle()
.fill(.orange)
.overlay(GeometryReader {
let side = sqrt($0.size.width * $0.size.width / 2)
VStack {
Rectangle().foregroundColor(.clear)
.frame(width: side, height: side)
.overlay(
Image(systemName: "trash")
.resizable()
.foregroundColor(.blue)
)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
})
.frame(width: 100, height: 100)
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.
For a rating display I am trying to split an Image up into two parts, a black and a red one and I would like the red part to take up a specific percentage of the whole image. The problem I am having is that the Rectangle is aligned to the centre of the other image and when changing the alignment of the ZStack to .leading, the Rectangle does move but unfortunately the masked area of the image does not change.
ZStack {
Image("Car")
Rectangle()
.foregroundColor(ColorManager.brand)
.frame(width: 20.0, height: 20.0)
.mask(Image("Car"))
}
Without alignment
ZStack(alignment: Alignment(horizontal: .leading, vertical: .center)) {
Image("Car")
Rectangle()
.foregroundColor(ColorManager.brand)
.frame(width: 20.0, height: 20.0)
.mask(Image("Car"))
}
With alignment
How could I change the alignment of the Rectangle to .leading, while also masking the leading part of the image?
edit: Desired effect
You need just make clipping at needed location. Here is a complete demo of possible approach (on custom CarView as I don't have car image like your).
Prepared with Xcode 12.1 / iOS 14.1
struct CarView: View {
var body: some View {
Image(systemName: "car").resizable()
.frame(width: 200, height: 80)
}
}
struct MaskShape: Shape {
var alignment: HorizontalAlignment = .center
func path(in rect: CGRect) -> Path {
switch alignment {
case .leading:
return Rectangle().path(in: rect.divided(atDistance: rect.width / 3, from: .minXEdge).slice)
case .center:
return Rectangle().path(in: rect.insetBy(dx: rect.width / 3, dy: 0))
case .trailing:
return Rectangle().path(in: rect.divided(atDistance: rect.width / 3, from: .maxXEdge).slice)
default:
return Rectangle().path(in: rect)
}
}
}
struct TestCarMaskView: View {
var body: some View {
VStack {
CarView()
.overlay(
Rectangle()
.foregroundColor(.red)
.mask(CarView())
.clipShape(MaskShape())
)
CarView()
.overlay(
Rectangle()
.foregroundColor(.red)
.mask(CarView())
.clipShape(MaskShape(alignment: .leading))
)
CarView()
.overlay(
Rectangle()
.foregroundColor(.red)
.mask(CarView())
.clipShape(MaskShape(alignment: .trailing))
)
}
}
}
so your code could look like (of course sizes of mask you can tune for your needs)
Image("Car")
.overlay(
Rectangle()
.foregroundColor(ColorManager.brand)
.mask(Image("Car"))
.clipShape(MaskShape(alignment: .leading))
)
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().