SwiftUI, shadow only for container - swift

For example, I have this view:
import SwiftUI
struct TarifsScreen: View {
var body: some View {
GeometryReader { geometry in
VStack {
VStack {
Spacer()
Text("Text1")
Spacer()
Text("Text2")
Spacer()
Text("Text3")
Spacer()
}
}
.frame(width: geometry.size.width, height: geometry.size.height)
.shadow(color: Color.white, radius: 10, x: 0, y: 0)
}
}
}
How can I apply shadow only for VStack, not for all elements inside VStack? May be I can do it with ZStack and two containers?

Add background and apply shadow to it, like in below example
VStack {
...
}
.background(Color.white // any non-transparent background
.shadow(color: Color.red, radius: 10, x: 0, y: 0)
)
.frame(width: geometry.size.width, height: geometry.size.height)

Also for a specific view:
var body: some View {
Text("SwiftUI is Awesome").padding().background(
Rectangle()
.fill(Color.white)
.cornerRadius(12)
.shadow(
color: Color.gray.opacity(0.7),
radius: 8,
x: 0,
y: 0
)
)
}
Reference from here.
Result:

If you don't want to add fills or backgrounds, you can better override the .shadow modifier on the child views with a .clear color, to "stop" the shadow modifier from propagating down the hierarchy.
VStack {
// View with shadow
...
VStack {
// View without shadow
...
}
.shadow(color: .clear, radius: 0) // Override shadow
}
.shadow(color: .black.opacity(0.3), radius: 10)
For example if you are using a png image, the other answers would render the alpha channel with a solid color, which is not ideal.

Related

How can make transition stay in own zIndex until ending the transition in SwiftUI?

I have code like this in below:
struct ContentView: View {
#State private var show: Bool = false
var body: some View {
ZStack {
Button("show") { show.toggle() }.foregroundColor(.black)
//.zIndex(1)
if (show) {
Circle()
.fill(.blue)
.frame(width: 100, height: 100)
.transition(AnyTransition.asymmetric(insertion: .offset(x: 0, y: 300), removal: .offset(x: 0, y: 300)))
.onTapGesture { show.toggle() }
//.zIndex(2)
}
}
.frame(width: 300, height: 300)
.padding()
.animation(.linear(duration: 1.5), value: show)
}
}
The issue with this code is that in insertion view stays in correct zIndex(in Top layer) but in removal goes to wrong zIndex(in Bottom layer), I can correct this issue with using direct zIndex but the goal of this question is to find a way without using zIndex modifier, I thing it is possible but not sure how, maybe it has something to do with transition.
This behavior seems like a bug.
I think this is a side effect of the fact that as soon as you remove the Circle() View, it is immediately gone, and the animation happens after the fact. So upon removal, there is instantly just one item in the ZStack and it is on top.
A workaround is to not completely remove a view from the ZStack. This can be accomplished by wrapping the if show { } with an HStack, VStack, or ZStack:
struct ContentView: View {
#State private var show: Bool = false
var body: some View {
ZStack {
Button("show") { show.toggle() }.foregroundColor(.black)
//.zIndex(1)
HStack { // Note: VStack and ZStack also work
if (show) {
Circle()
.fill(.blue)
.frame(width: 100, height: 100)
.transition(AnyTransition.asymmetric(insertion: .offset(x: 0, y: 300), removal: .offset(x: 0, y: 300)))
.onTapGesture { show.toggle() }
//.zIndex(2)
}
}
}
.frame(width: 300, height: 300)
.padding()
.animation(.linear(duration: 1.5), value: show)
}
}
In this case, while the Circle() View is immediately removed, the HStack to which it belongs remains, and it remains in the same position in the ZStack thus fixing the animation.
That said, I'm not sure why adding explicit .zIndex() modifiers helps.
Note: Wrapping if show() { } in a Group does not help because Group is not a View.

SwiftUI - How to center a component in a horizontal ScrollView when tapped?

I have a horizontal ScrollView, and within it an HStack. It contains multiple Subviews, rendered by a ForEach. I want to make it so that when these Subviews are tapped, they become centered vertically in the view. For example, I have:
ScrollView(.horizontal) {
HStack(alignment: .center) {
Circle() // for demonstration purposes, let's say the subviews are circles
.frame(width: 50, height: 50)
Circle()
.frame(width: 50, height: 50)
Circle()
.frame(width: 50, height: 50)
}
.frame(width: UIScreen.main.bounds.size.width, alignment: .center)
}
I tried this code:
ScrollViewReader { scrollProxy in
ScrollView(.horizontal) {
HStack(alignment: .center) {
Circle()
.frame(width: 50, height: 50)
.id("someID3")
.frame(width: 50, height: 50)
.onTapGesture {
scrollProxy.scrollTo(item.id, anchor: .center)
}
Circle()
.frame(width: 50, height: 50)
.id("someID3")
.frame(width: 50, height: 50)
.onTapGesture {
scrollProxy.scrollTo(item.id, anchor: .center)
}
...
}
}
But it seemingly had no effect. Does anyone know how I can properly do this?
You can definitely do this with ScrollView and ScrollViewReader. However, I see a couple of things that could cause problems in your code sample:
You use the same id "someID3" twice.
I can't see where your item.id comes from, so I can't tell if it actually contains the same id ("someID3").
I don't know why you have two frames with the same bounds on the same view area. It shouldn't be a problem, but it's always best to keep things simple.
Here's a working example:
import SwiftUI
#main
struct MentalHealthLoggerApp: App {
var body: some Scene {
WindowGroup {
ScrollViewReader { scrollProxy in
ScrollView(.horizontal) {
HStack(alignment: .center, spacing: 10) {
Color.clear
.frame(width: (UIScreen.main.bounds.size.width - 70) / 2.0)
ForEach(Array(0..<10), id: \.self) { id in
ZStack(alignment: .center) {
Circle()
.foregroundColor(.primary.opacity(Double(id)/10.0))
Text("\(id)")
}
.frame(width: 50, height: 50)
.onTapGesture {
withAnimation {
scrollProxy.scrollTo(id, anchor: .center)
}
}
.id(id)
}
Color.clear
.frame(width: (UIScreen.main.bounds.size.width - 70) / 2.0)
}
}
}
}
}
}
Here you can see it in action:
[EDIT: You might have to click on it if the GIF won't play automatically.]
Note that I added some empty space to both ends of the ScrollView, so it's actually possible to center the first and last elements as ScrollViewProxy will never scroll beyond limits.
Created a custom ScrollingHStack and using geometry reader and a bit calculation, here is what we have:
struct ContentView: View {
var body: some View {
ScrollingHStack(space: 10, height: 50)
}
}
struct ScrollingHStack: View {
var space: CGFloat
var height: CGFloat
var colors: [Color] = [.blue, .green, .yellow]
#State var dragOffset = CGSize.zero
var body: some View {
GeometryReader { geometry in
HStack(spacing: space) {
ForEach(0..<15, id: \.self) { index in
Circle()
.fill(colors[index % 3])
.frame(width: height, height: height)
.overlay(Text("\(Int(dragOffset.width))"))
.onAppear {
dragOffset.width = geometry.size.width / 2 - ((height + space) / 2)
}
.onTapGesture {
let totalItems = height * CGFloat(index)
let totalspace = space * CGFloat(index)
withAnimation {
dragOffset.width = (geometry.size.width / 2) - (totalItems + totalspace) - ((height + space) / 2)
}
}
}
}
.offset(x: dragOffset.width)
.gesture(DragGesture()
.onChanged({ dragOffset = $0.translation})
)
}
}
}

How to animate view separately in SwiftUI?

I created a red rectangle on a black rectangle. I wanna when I press the toggle, the red one scale with animation, and they both move with animation, two animation work separately but not like this:
struct AnimationTest: View {
#State var toggled = false
var body: some View {
VStack {
ZStack {
Rectangle()
.frame(width: 200, height: 200)
.foregroundColor(Color.black)
Rectangle()
.frame(width: 50, height: 50)
.foregroundColor(Color.red)
.scaleEffect(toggled ? 2.0 : 1.0)
.animation(.spring(response: 0.3, dampingFraction: 0.3), value: toggled)
}
.offset(x: 0.0, y: toggled ? -50 : 0.0)
.animation(.easeOut, value: toggled)
Toggle("Toggle", isOn: $toggled)
.padding(20.0)
}
}
}

Overlay text on Image SwiftUI and WidgetKit

I'm currently creating a widget for my new iOS 14 app and I encountered a weird problem when I'm trying to add and Overlay with Text on top of my Image. The overlay is correctly working on a blank SwiftUI project but it's not working on the Widget.
Below you can find the code that I'm using. KFImage is from KingFisherSwiftUI, but if you want to test it, you can just add a static Image from the Asset or from the System.
Thanks!
struct NewItems: View {
var body: some View {
KFImage(URL(string: "https://img.freepik.com/free-vector/triangular-dark-polygonal-background_23-2148261453.jpg?size=626&ext=jpg")! )
.resizable()
//.frame(width: 200, height: 200, alignment: .center)
.overlay(TextView(), alignment: .bottomTrailing)
.cornerRadius(20)
}
}
struct TextView: View {
var body: some View {
ZStack {
Text("Hello World Beautiful wallpaper")
.foregroundColor(.white)
.shadow(color: .black, radius: 1, x: 1.0, y: 1.0)
.padding(6)
.opacity(0.8)
}
.background(Color.black)
.cornerRadius(10)
.padding(3)
}
}
[EDIT]
Below the result of the code. Looks like the image is on top of the text, even though it supposed to be in overlay.
In the end after multiple tries I managed to find an alternative solution.
struct TextView: View {
let entry: HelloWorldEntry
var body: some View {
ZStack {
Text("SOME TEXT")
.foregroundColor(.white)
.shadow(color: .black, radius: 1, x: 1.0, y: 1.0)
.padding(6)
.opacity(0.8)
}
.background(Color.black)
.cornerRadius(10)
.padding(6)
}
}
struct NewTest: View {
let entry: HelloWorldEntry
var body: some View {
VStack {}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(
KFImage(URL(string: "https://example.com/image.png")!)
.resizable()
.scaledToFill()
)
.overlay(TextView(entry: entry), alignment: .bottomTrailing)
}
}

Align rectangle to be within certain distance of safe area bottom edge SWIFTUI

I am used to Interface Builder and layout constraints but now I want to convert my app to Swift UI. What I am trying to do right now is align the top edge of the view marked with a 1 to be within a certain distance of the safe area bottom edge (marked with a 2) so that the top edge that is now at 1 will then be at position 3. I tried using spacers but then it will look different on smaller devices such as an iPhone 8. In IB I could have used a simple layout constraint. How does this work in Swift UI? I have attached the relevant code and an image. Thank you for your help.
struct ContentView: View {
init() {
UINavigationBar.appearance().backgroundColor = .orange
}
var body: some View {
NavigationView {
VStack{
Spacer()
ZStack{
Rectangle()
.fill(Color(hue: 0, saturation: 0, brightness: 0, opacity: 0.1))
Image("")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 100, height: 150)
.clipShape(Circle())
.overlay(Circle().stroke(Color.white, lineWidth: 4))
.shadow(radius: 10)
}.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.width, alignment: .center)
Spacer(minLength: 100)
ZStack(alignment: .bottom){
ExtractedView()
.edgesIgnoringSafeArea(.bottom)
}
}
.navigationBarTitle(Text("You See"))
.navigationBarHidden(false)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.previewDevice("iPhone")
}
}
struct ExtractedView: View {
#State private var name: String = ""
var body: some View {
VStack{
ZStack(alignment: .top){
VStack{
RoundedRectangle(cornerRadius: 50)
.frame(width: 60, height: 7)
.padding(.top)
Button(action: /*#START_MENU_TOKEN#*/{}/*#END_MENU_TOKEN#*/) {
Text("Start advertising")
.font(.headline)
.foregroundColor(Color.black)
}.padding(.top)
TextField("Test", text: $name)
.padding(.all)
.background(Color.white.cornerRadius(20))
.padding()
}
RoundedRectangle(cornerRadius: 30)
.fill(Color(hue: 0, saturation: 0, brightness: 0, opacity: 0.1))
.zIndex(-5)
}
}
}
}
Ok, so I was able to solve my problem. The trick is to create two VStacks with different frame alignments. The outer VStack has top alignment so that the Discover view can be at the top. The inner VStack has a bottom alignment so that the sheet can be pulled up from the bottom. Space will be filled from the bottom up in this case.
VStack{
Discover()
.padding(.top, 60.0)
VStack{
Text("Recent Messages:")
.font(.headline)
DetailSheet()
.offset(x: 0, y: 0)
}.frame(width: g.size.width, height: (g.size.height - g.size.width - 80 + 200), alignment: .bottom)
}
.frame(width: g.size.width, height: g.size.height, alignment: .top)
you can try this:
offset -10 is just your offset you want to have....
i hope i understood you right, i am not so sure...
var body: some View {
VStack{
// ZStack(alignment: .top){
VStack{
RoundedRectangle(cornerRadius: 50)
.frame(width: 60, height: 7)
.padding(.top)
Button(action: /*#START_MENU_TOKEN#*/{}/*#END_MENU_TOKEN#*/) {
Text("Start advertising")
.font(.headline)
.foregroundColor(Color.black)
}.padding(.top)
TextField("Test", text: $name)
.padding(.all)
.background(Color.white.cornerRadius(20))
.padding()
}.background(RoundedRectangle(cornerRadius: 30)
.fill(Color(hue: 0, saturation: 0, brightness: 0, opacity: 0.1))
).offset(y:-10)
}
// }