Skip to main content

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