Dynamische Schatteneffekte
Beschreibung
Diese View zeigt eine Liste von Karten an, deren Schatten sich dynamisch an die aktuelle Scrollposition anpassen. Je nachdem, wie weit eine Karte von der Bildschirmmitte entfernt ist, verändert sich Intensität und Richtung des Schattens – dies erzeugt einen realistischen Tiefeneffekt und betont visuelle Hierarchien innerhalb der Liste.
🔍 Zweck
- Verbesserung der Übersichtlichkeit in langen Listen durch Hervorhebung aktiver Elemente
- Darstellung von Fokus oder Auswahl im Inhaltsverzeichnis einer App
- Visuelle Betonung im Task-/Notizmanagement (ToDo-Karten etc.)
- Modernes UI/UX für Newsfeed-, Magazin- oder Kalenderansichten
- Dynamische Effekte für Präsentationskarten und Coverflows
🖥️ Betriebssystem
iOS
📄 Codebeispiel
import SwiftUI
struct DynamicShadowListView: View {
let items = (1...16).map { "Card \($0)" }
var body: some View {
GeometryReader { outerGeo in
ScrollView(.vertical, showsIndicators:false) {
VStack(spacing:24) {
ForEach(Array(items.enumerated()), id:\.element) { idx,item in
GeometryReader{ geo in
DynamicShadowCard(
text:item,
cardFrame:CGRect(origin:.zero,size:.init(width:geo.size.width,height:geo.size.height)),
containerFrame:CGRect(origin:.zero,size:.init(width:outerGeo.size.width,height:pxCardHeight)),
cellGlobalMinY:
geo.frame(in:.global).minY,
containerMidY:
outerGeo.frame(in:.global).midY
)
}
// Fixed height for each card; allows easy calculation of position effects.
.frame(height:pxCardHeight)
}
}
.padding()
}
.background(Color(.systemGroupedBackground).edgesIgnoringSafeArea(.all))
}
}
}
// Card constants:
let pxCardHeight : CGFloat = 110
struct DynamicShadowCard : View {
let text:String
let cardFrame:CGRect // local frame of the card within its own GeometryReader.
let containerFrame:CGRect // ScrollView frame (outer).
let cellGlobalMinY : CGFloat // Top Y-position of this card in global coordinates.
let containerMidY : CGFloat // Middle Y of ScrollView area.
var body : some View {
// Calculate how far from vertical center this card is (-1 ... +1):
let centerDistanceNorm = min(abs((cellGlobalMinY + cardFrame.height/2 - containerMidY) / (containerFrame.height/2)),1)
// Shadow intensity and lateral shift:
let shadowOpacity = max(0.18, (1-centerDistanceNorm)*0.45) // Near center = strong shadow.
let horizontalShiftRange : CGFloat = 18 // Max shadow x-offset when far from center.
// Shadow shifts left/right depending on vertical position:
let shadowX =
sin((cellGlobalMinY + cardFrame.height/2 - containerMidY)/containerFrame.height*CGFloat.pi)*horizontalShiftRange
ZStack(alignment:.leading){
RoundedRectangle(cornerRadius:pxCardHeight/3,style:.continuous)
.fill(Color.white)
//.overlay(RoundedRectangle(cornerRadius:pxCardHeight/3).stroke(Color.secondary.opacity(0.03),lineWidth:pxCardHeight/12))
// Dynamic shadow based on scroll position:
.shadow(color:.black.opacity(shadowOpacity),
radius:(20 - centerDistanceNorm*12),
x:(shadowX),y:(10 + centerDistanceNorm*12))
Text(text)
.font(.system(size:pxCardHeight/3.5,weight:.bold))
.padding([.leading,.top,.bottom],20)
} // End ZStack
} // End body
}
#Preview {
DynamicShadowListView()
}