Subview have parent's shadow, even with a background - ios

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)

Related

SwiftUI - Fill image of background cell keeping aspect ratio

In the wonderful world of SwiftUI, I have a View that I use as a cell. I intend to reproduce the Layout of a previous application of mine, with Autolayout, not SwiftUI, in which the background image filled the entire cell, adjusting to the width and losing pieces of image above and below.
In my new app, the code in SwiftUI is the following:
struct CharacterRow2: View {
var character: Character
var body: some View {
Text(character.name)
.font(Font.custom(avengeanceHeroicAvengerNormal, size: 30))
.foregroundColor(.white)
.baselineOffset(-10)
.shadow(color: .black, radius: 1, x: -1, y: 1)
.frame(width: UIScreen.main.bounds.width, height: 140)
.background {
WebImage(url: extractImage(data: character.thumbnail))
.resizable()
.frame(width: UIScreen.main.bounds.width, height: 150)
}
}
}
With this code, my app looks like this:
I tried to add scaledToFill():
WebImage(url: extractImage(data: character.thumbnail))
.resizable()
.scaledToFill()
.frame(width: UIScreen.main.bounds.width, height: 150)
But this is the result:
I'm stuck...
Thank you in advance!
In this case, you are simply using too many frames. And using them incorrectly. You should avoid using UIScreen.main.bounds in SwiftUI, especially in something like a view cell. By doing this, the cell will not behave properly with other views, and can cause UI issues that would be difficult to trace.
The simplest way to get the behavior you want is this:
Text(character.name)
.font(.largeTitle)
.foregroundColor(.white)
.baselineOffset(-10)
.shadow(color: .black, radius: 1, x: -1, y: 1)
.frame(height: 140)
.frame(maxWidth: .infinity) // use .infinity for max width to make it
// as large as the space offered by the
// parent view
.background {
WebImage(url: extractImage(data: character.thumbnail))
.resizable()
// .frame(width: UIScreen.main.bounds.width, height: 150) <-- Remove this frame altogether
.scaledToFill()
}
.clipped // This keeps the image from being larger than the frame
This will size to be as wide as the parent view allows it to be. Leaving the UIScreen.main.bounds.width could cause it to be larger than the parent view and cause partial eclipsing.
In your last example the images are overlaying each other. This is due to calling scaleToFill(). The images are now ignoring their frame boundaries regarding the height. Adding .clipped solves the problem.
struct CharacterRow2: View {
var character: String
var body: some View {
Text(character)
.font(.largeTitle)
.foregroundColor(.white)
.baselineOffset(-10)
.shadow(color: .black, radius: 1, x: -1, y: 1)
.frame(width: UIScreen.main.bounds.width, height: 140)
.background {
Image(character)
.resizable()
.scaledToFill()
.frame(width: UIScreen.main.bounds.width, height: 150)
.clipped() // <-- Add this
}
}
}
It seems this will work only with a ForEach inside a ScrollView. Using a List seems to breack the vertical frame boundary.
struct ContentView: View{
let content = ["1.jpg", "2.jpg", "3.jpg" ]
var body: some View{
//These look really weird
// List(content, id: \.self){ name in
// CharacterRow2(character: name)
// }
// List{
// VStack{
// ForEach(content, id: \.self){ name in
// CharacterRow2(character: name)
// }
// }
// }
//working
ScrollView{
VStack{
ForEach(content, id: \.self){ name in
CharacterRow2(character: name)
.padding()
}
}
}
}
}
Result:

How to resize a rectangle to fit a device in SwiftUI

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()
}
}

How to restrict drag gesture to particular frame only in swiftUI

I want to drag circle shape only within a frame horizontal. I try below code you can see shape work perfectly horizontal and also left side but when drag right side shape drag up to out of device frame. I check so many answer but did't work anything. also try to add .onEnded but don't work.
please help me
thanks in advance
here is a code that i tried
struct ProgressIndicator: View {
#State private var position = CGSize.zero
var body: some View {
GeometryReader { geometry in
VStack(alignment: .leading) {
ZStack {
Circle()
.foregroundColor(Color.blue)
.opacity(0.3)
.frame(width: 40, height: 40, alignment: .leading)
.padding(.bottom, -12)
}
.gesture(
DragGesture()
.onChanged({ (value) in
self.position.width += value.location.x
})
)
.offset(x: self.position.width - 20, y: 0)
}
}
}
}
here is screenshot of circle shape for drag to left and right
You can restrict the position with GeometryReader:
self.position.width = min(self.position.width + value.location.x, geometry.size.width / 2)
Here's the result from Playgrounds:
https://imgur.com/FA5xyAz

SwiftUI edges visible after using overlay

I'm trying to create rounded edges in one of my views using overlay.
.background(Color.gray.opacity(0.2))
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.gray.opacity(0.2), lineWidth: 1)
)
The issue is that i still can see the edges on the background and also the stroke. How to crop the edges?
Your view will not be tappable through overlay, even with transparency, so the solution is to use clip shape and background as shown below
struct DemoRoundRectView: View {
var body: some View {
Text("DEMO")
.frame(width: 100, height: 50)
.background(Color.gray.opacity(0.2))
.clipShape(RoundedRectangle(cornerRadius: 10)) // clip corners
.background(
RoundedRectangle(cornerRadius: 10) // stroke border
.stroke(Color.gray.opacity(0.2), lineWidth: 1)
)
}
}
struct ContentView: View {
var body: some View {
Rectangle()
.fill(Color.orange)
.frame(width: 200, height: 200, alignment: .center)
.cornerRadius(10)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.red, lineWidth: 1)
)
} }
try this.
It seems that right now your modifiers are something like:
.cornerRadius(10)
.background(Color.gray.opacity(0.2))
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.gray.opacity(0.2), lineWidth: 1)
)
Instead, you need to change the order of the view modifiers, as order matters! You are rounding the corners then applying a background, whereas you should be applying the background so that the corner radius can then clip that.
Try this instead:
.background(Color.gray.opacity(0.2))
.cornerRadius(10)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.gray.opacity(0.2), lineWidth: 1)
)

How To Position Views Relative To Their Top Left Corner In SwiftUI

How do I position views relative to their top left corner in swiftUI? The "position" modifier moves the views relative to their center coordinates. So .position(x: 0, y: 0) places a views center coordinate in the top left of the screen.
I want to place a views top left coordinate in the top left of the screen, How do I do this?
struct HomeView: View {
var body: some View {
VStack(alignment: .leading) {
Text("Top Text")
.font(.system(size: 20))
.fontWeight(.medium)
Text("Bottom Text")
.font(.system(size: 12))
.fontWeight(.regular)
}
.position(x: 0, y: 0)
}
}
Currently, the left half of my view is cut off the screen, how do I prevent this? Also, how do I align relative to the safe area?
I miss UIKit 😪
#Asperi 's answer will solve the problem. But, I think we should use Spacer() rather than Color.clear and ZStack.
Spacer is specifically designed for these scenarios and makes the code easier to understand.
struct HomeView: View {
var body: some View {
HStack {
VStack(alignment: .leading) {
Text("Top Text")
.font(.system(size: 20))
.fontWeight(.medium)
Text("Bottom Text")
.font(.system(size: 12))
.fontWeight(.regular)
Spacer()
}
Spacer()
}
}
}
SwiftUI layout system is different from UIKit.
It asks each child view to calculate its own size based on the bounds of its parent view. Next, asks each parent to position its children within its own bounds.
https://www.hackingwithswift.com/books/ios-swiftui/how-layout-works-in-swiftui
If I correctly understand your goal the .position is not appropriate instrument for it. SwiftUI layout works better without hardcoding.
Here is possible solution. Tested with Xcode 11.4 / iOS 13.4
struct HomeView: View {
var body: some View {
ZStack(alignment: .topLeading) {
Color.clear
VStack(alignment: .leading) {
Text("Top Text")
.font(.system(size: 20))
.fontWeight(.medium)
Text("Bottom Text")
.font(.system(size: 12))
.fontWeight(.regular)
}
}.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
You can use:
offset(x: CGFloat = 0, y: CGFloat = 0) -> some View
So you will be relative to current object position.
https://developer.apple.com/documentation/swiftui/gaugestyleconfiguration/currentvaluelabel-swift.struct/offset(x:y:)

Resources