SwiftUI: Shadow on view in VStack? - swiftui

I have the following code:
struct ViewA: View {
var body: some View {
Rectangle()
.fill(Color(.yellow))
}
}
struct ViewB: View {
var body: some View {
Rectangle()
.fill(Color(.white))
}
}
struct ViewC: View {
var body: some View {
Rectangle()
.fill(Color(.blue))
}
}
struct TestView: View {
var body: some View {
GeometryReader { geo in
VStack(spacing: 0) {
ViewA()
.frame(width: geo.size.width, height: geo.size.width/4, alignment: .center)
.shadow(color: .black, radius: 10, x: 0, y: 10)
ViewB()
ViewC()
.frame(width: geo.size.width, height: 100, alignment: .center)
.shadow(color: .black, radius: 10, x: 0, y: 0)
.opacity(0.8)
}
}
}
}
It draws:
The shadow at the bottom Blue over White view (ViewC over ViewB) shows up.
I want the yellow view (ViewA) to drop a shadow on top of the white view.
I want the white view to appear as if it were under both the top view (yellow) and the bottom view (blue).
I am not sure how to organize the views so that the top view also drops shadow on the middle view.
How would I accomplish this?

You could apply zindex modifier to ViewA. For example .zIndex(.infinity). It will put ViewA on top of everything.
https://developer.apple.com/documentation/swiftui/view/zindex(_:)

If you place the views in a ZStack with ViewB behind ViewA and ViewC you will see the shadow:
GeometryReader { geo in
ZStack {
ViewB()
VStack(spacing: 0) {
ViewA()
.frame(
width: geo.size.width,
height: geo.size.width/4,
alignment: .center)
.shadow(
color: .black,
radius: 10,
x: 0, y: 0)
Spacer()
ViewC()
.frame(
width: geo.size.width,
height: 100,
alignment: .center)
.shadow(
color: .black,
radius: 10,
x: 0, y: 0)
.opacity(0.8)
}
}
}

Related

SwiftUI: Adding a Shadow to View in List/Grid is Lagging UI

When adding a shadow to my view in a Grid, the scrolling experience is bad. It feels like the Frame Rate is dropping. I came across this post, option 1 made my whole background for my view the same color as my shadow. I Don't really know how to implement UIViewRepresentable in option 2.
So how would I be able to use UIViewRepresentable, or is there a better way to do this.
MRE CODE
struct ContentView: View {
#State var gridSpacing: CGFloat = 8
let columns: [GridItem] = [GridItem(.flexible(),spacing: 8), GridItem(.flexible(),spacing: 8),GridItem(.flexible(),spacing: 8)]
var body: some View {
ScrollView {
LazyVGrid(columns: columns, spacing: gridSpacing) {
ForEach(0..<200) { x in
VStack(spacing: 8) {
Image("sumo-deadlift") //<----- Replace Image
.resizable()
.scaledToFit()
.background(.yellow)
.clipShape(Circle())
.padding(.horizontal)
.shadow(color: .blue, radius: 2, x: 1, y: 1) //<----- Comment/Uncomment
Text("Sumo Deadlift")
.font(.footnote.weight(.semibold))
.multilineTextAlignment(.center)
.lineLimit(2)
.frame(maxWidth: .infinity)
Text("Legs")
.font(.caption)
.foregroundStyle(.secondary)
}
.padding(.all)
.background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 20, style: .continuous))
.mask(RoundedRectangle(cornerRadius: 20))
.shadow(color: .red, radius: 2, x: 1, y: 1) //<----- Comment/Uncomment
}
}
.padding(.all, 8)
}
.background(.ultraThinMaterial)
}
}

New to SwiftUI trying to understand layout changes without (I guess it doesn't have ) Layout constraints

The problem is I don't understand how to fix my Content View to the screen and handle things like rotation or even how to make other screen sizes dynamic. Below is my entire View code. When I create a new app out of the box it looks fine in preview, the way I like it. However, screen sizes change and all I know to do is put magic numbers, because there is no superview or frame to size from. Also I can't seem to clip at screen edges. So my background view is way to big. How is this handled in SwiftUI?
import SwiftUI
import AuthenticationServices
struct LoginScreen: View {
var body: some View {
ZStack{
backgroundLayout
loginLayout
}
}
var loginLayout: some View {
VStack {
welcomeText
signInWithAppleButton
}
}
var backgroundLayout: some View {
ZStack{
Rectangle()
.foregroundColor(.init(.sRGB, red: 0, green: 0.750, blue: 0.750, opacity: 0.25))
Circle()
.foregroundColor(.blue)
.offset(x: 200, y: -200)
.aspectRatio(1/3, contentMode: .fill)
.clipped()
Circle()
.foregroundColor(.green)
.offset(x: -200, y: 200)
.aspectRatio(1/3, contentMode: .fill)
.clipped()
}
}
var welcomeText: some View {
HStack {
VStack {
Text("Sign Up")
.font(.largeTitle)
.bold()
Text("Sign In")
.font(.title)
.bold()
Text("Get Started!")
.font(.title2)
.bold()
}
.offset(x: -100, y: -150)
}
}
var signInWithAppleButton: some View {
SignInWithAppleButton(
.continue,
onRequest: { request in
request.requestedScopes = [.fullName, .email]
},
onCompletion: { result in
switch result {
case .success (let authenticationResults):
print("Authorization successful! :\(authenticationResults)")
case .failure(let error):
print("Authorization failed: " + error.localizedDescription)
}
}
)
.offset(x: 0, y: 150)
.frame(width: 200, height: 50, alignment: .center)
}
}
struct Login_Preview: PreviewProvider {
static var previews: some View {
LoginScreen()
}
}
After trying several things this is all I could come up with. It's not perfect and certainly not ideal, but at least it work somewhat as expected.
import SwiftUI
import AuthenticationServices
struct LoginScreen: View {
var body: some View {
loginLayout.background(backgroundLayout)
.statusBar(hidden: true)
}
var loginLayout: some View {
GeometryReader {geo in
VStack {
welcomeText.padding(.top, geo.size.height / 10)
Spacer()
signInWithAppleButton
.padding(.bottom, geo.size.height / 10)
}
}
}
var backgroundLayout: some View {
GeometryReader { geo in
let circleSize = min(geo.size.width, geo.size.height)
ZStack {
Rectangle()
.foregroundColor(.init(.sRGB, red: 0, green: 0.750, blue: 0.750, opacity: 0.25))
Circle()
.foregroundColor(.blue)
.offset(x: circleSize / 1.75,
y: -geo.size.height / 2.5)
.frame(width: circleSize, height: circleSize, alignment: .center)
Circle()
.foregroundColor(.green)
.offset(x: -circleSize / 1.75, y: geo.size.height / 2.5)
.frame(width: circleSize, height: circleSize, alignment: .center)
}
.frame(width: geo.size.width, height: geo.size.height, alignment: .top)
}
}
var welcomeText: some View {
GeometryReader { geo in
HStack {
VStack {
Text("Sign Up")
.font(.largeTitle)
.bold()
Text("Sign In")
.font(.title)
.bold()
Text("Get Started!")
.font(.title2)
.bold()
}
.padding(.leading, geo.size.width / 8)
Spacer()
}
}
}
var signInWithAppleButton: some View {
SignInWithAppleButton(
// Add this below for Continue with Apple button
.continue,
onRequest: { request in
request.requestedScopes = [.fullName, .email]
},
onCompletion: { result in
switch result {
case .success (let authenticationResults):
print("Authorization successful! :\(authenticationResults)")
case .failure(let error):
print("Authorization failed: " + error.localizedDescription)
}
}
)
.frame(width: 200, height: 50, alignment: .center)
}
}
struct Login_Preview: PreviewProvider {
static var previews: some View {
LoginScreen()
}
}

Implementing Box-Shadow in SwiftUI

I'm trying to implement the following css code in SwiftUI
background: #FFFFFF;
box-shadow: 0px 10px 20px rgba(223, 227, 240, 0.2);
border-radius: 27.5px;
It should look like this (barely visible shadow below the button):
Here's the code i'm currently experimenting with:
import SwiftUI
struct RegisterView: View {
var body: some View {
VStack(spacing: 0) {
HStack(alignment: .top) {
Text("Register")
.font(.custom("NotoSans-Regular", size: 24))
.fontWeight(.bold)
.foregroundColor(Color.tertiaryTitleColor)
}
.frame(width: 299, height: 39)
.padding(.top, 78)
HStack {
BroviderButton(imageName: "googleLogo")
BroviderButton(imageName: "facebookLogo")
}
.padding(.top, 47)
Spacer()
}
.frame(maxHeight: .infinity)
.frame(maxWidth: .infinity)
.background(Color.loginBackgroundColor)
.ignoresSafeArea()
}
}
struct RegisterView_Previews: PreviewProvider {
static var previews: some View {
RegisterView()
}
}
struct BroviderButton: View {
var imageName: String
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 27.5)
.stroke(Color.dropShadowColor,lineWidth: 1)
.frame(width: 133, height: 56, alignment: .center)
.shadow(color: .black, radius: 2, x: 1, y: 2)
VStack {
VStack {
Image(imageName)
.resizable()
.scaledToFit()
}
.frame(height: 28, alignment: .center)
}
.frame(width: 133, height: 56, alignment: .center)
.foregroundColor(.blue)
.background(Color.loginButtonBackgroundColor)
.cornerRadius(27.5)
.opacity(1)
}
}
}
And here's how it looks like atm:
I don't understand how to translate x:10 & y:20 to SwiftUI code and also can't find the way to blur the shadow (20%). Any ideas are welcome.
Also can't figure out why that RoundedRectangle stays visible after i put VStack on top of it.. I was thinking it will hide everything but the shadow below the button.
As far as I see there are two issues here to be fixed (although of course I don't have your colors and images):
ZStack {
RoundedRectangle(cornerRadius: 27.5)
.fill(Color.dropShadowColor) // << here fill, not stroke !!
.frame(width: 133, height: 56, alignment: .center)
.shadow(color: .black, radius: 2, x: 0, y: 2) // << no offset by x
VStack {
VStack {
Image(imageName)
.resizable()
.scaledToFit()
}
.frame(height: 28, alignment: .center)
}
.frame(width: 133, height: 56, alignment: .center)
.foregroundColor(.blue)
.background(Color.loginButtonBackgroundColor) // << should be opaque color !!
.cornerRadius(27.5)
.opacity(1)
}

Changing the anchor point for a View when placing using .position()

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

SwiftUI, is there a traitCollectionDidChange-like method in a View?

I'm researching SwiftUI to see if it fits our compony's use case. However, I find it rather bare bones and wonder wether I am missing some key insights.
Currently I am researching if V/HStack's can respond dynamically to Accesability font-size changes.
The issue is that I need to give the items a frame, it does not automatically pin the outer Stacks items-view constraints to their inner content. When the user sets the font-size to full blown Bananas-mode the UI just breaks.
struct PatientHistoryView: View {
#State var model : PatientHistoryModel = historyModel;
var body : some View {
GeometryReader { g in
ScrollView(.vertical, showsIndicators: true) {
List
{
ForEach(self.model.data)
{ labResults in
LabDetailView(labeResults: labResults)
}
}.frame(width: 300, height: g.size.height).background(Color.clear)
}
}
}
}
adding these properties do not seem to affect the Font-scaling issues:
struct PatientHistoryView: View {
#State var model : PatientHistoryModel = historyModel;
var body : some View {
GeometryReader { g in
ScrollView(.vertical, showsIndicators: true) {
List
{
ForEach(self.model.data)
{ labResults in
LabDetailView(labeResults: labResults).frame(minWidth: 200, idealWidth: 400, maxWidth: 600, minHeight: 50, idealHeight: 100, maxHeight: 600)
}
}.frame(width: 300, height: g.size.height).background(Color.clear)
}
}
}
}
Here is the labDetailView
struct LabDetailView: View {
var labeResults : LabResults
var color : Color = .red
var spacer : some View {
Spacer().frame(width: 10, height: 0)
}
var body : some View {
GeometryReader { g in
HStack
{
Group
{
VerticalLineView().frame(width: 10, height: g.size.height, alignment: .leading).background(self.color)
self.spacer
self.spacer
LineStack(lineColor: .white, lineHeight: 2).frame(width: 20, height: 20, alignment: .center)
self.spacer
self.spacer
}
VStack(alignment: .leading, spacing: 8)
{
Text(self.labeResults.title).fontWeight(.bold)
Text(self.labeResults.type)
}
Spacer()
VStack(alignment: .center)
{
Text(self.labeResults.max.asString)
Spacer()
Text(self.labeResults.avg.asString).fontWeight(.bold).font(.system(size: 20))
Spacer()
Text(self.labeResults.min.asString)
}.frame(width: 50, height: g.size.height, alignment: .center)
self.spacer
self.spacer
}.frame(width: g.size.width, height: g.size.height, alignment: .leading)
}
}
}
In UIkit these issues can be resolved in the traitCollectionDidChange hook. However, in Swift UI I just cannot find an equivalent.
Doe anybody have any ideas?
Thanks!