Skip to main content

How to create 3D effects like Cover Flow using ScrollView and GeometryReader

About 2 minSwiftSwiftUIArticle(s)bloghackingwithswift.comcrashcourseswiftswiftuixcodeappstore

How to create 3D effects like Cover Flow using ScrollView and GeometryReader 관련

SwiftUI by Example

Back to Home

How to create 3D effects like Cover Flow using ScrollView and GeometryReader | SwiftUI by Example

How to create 3D effects like Cover Flow using ScrollView and GeometryReader

Updated for Xcode 15

If we combine GeometryReader with any view that can change position – such as something that has a drag gestures or is inside a List – we can create 3D effects that look great on the screen. GeometryReader allows us to read the coordinates for a view, and feed those values directly into a rotation3DEffect() modifier.

For example, we could create a Cover Flow-style scrolling effect by stacking up many text views horizontally in a scroll view, then applying rotation3DEffect() so that as they move in the scroll view they gently spin around, like this:

ScrollView(.horizontal, showsIndicators: false) {
    HStack(spacing: 0) {
        ForEach(1..<20) { num in
            VStack {
                GeometryReader { geo in
                    Text("Number \(num)")
                        .font(.largeTitle)
                        .padding()
                        .background(.red)
                        .rotation3DEffect(.degrees(-Double(geo.frame(in: .global).minX) / 8), axis: (x: 0, y: 1, z: 0))
                        .frame(width: 200, height: 200)
                }
                .frame(width: 200, height: 200)
            }
        }
    }
}

Download this as an Xcode projectopen in new window

You don't always need to use GeometryReader to get interesting effects like – you could something similar with a DragGesture(), for example. So, this code creates a card-like rectangle that can be dragged around in both X and Y axes, and uses two rotation3DEffect() modifiers to apply values from that drag:

struct ContentView: View {
    @State var dragAmount = CGSize.zero

    var body: some View {
        VStack {
            Rectangle()
                .fill(LinearGradient(gradient: Gradient(colors: [.yellow, .red]), startPoint: .topLeading, endPoint: .bottomTrailing))
                .frame(width: 200, height: 150)
                .clipShape(RoundedRectangle(cornerRadius: 20))
                .rotation3DEffect(.degrees(-Double(dragAmount.width) / 20), axis: (x: 0, y: 1, z: 0))
                .rotation3DEffect(.degrees(Double(dragAmount.height / 20)), axis: (x: 1, y: 0, z: 0))
                .offset(dragAmount)
                .gesture(
                    DragGesture()
                        .onChanged { dragAmount = $0.translation }
                        .onEnded { _ in
                            withAnimation(.spring()) {
                                dragAmount = .zero
                            }
                        }
                )
        }
        .frame(width: 400, height: 400)
    }
}

Download this as an Xcode projectopen in new window

As you drag the card around you'll see it rotates to give a perspective effect.

Similar solutions…
How to provide relative sizes using GeometryReader | SwiftUI by Example

How to provide relative sizes using GeometryReader
How to add Metal shaders to SwiftUI views using layer effects | SwiftUI by Example

How to add Metal shaders to SwiftUI views using layer effects
SwiftUI tips and tricks | SwiftUI by Example

SwiftUI tips and tricks
How to stack modifiers to create more advanced effects | SwiftUI by Example

How to stack modifiers to create more advanced effects
All SwiftUI property wrappers explained and compared | SwiftUI by Example

All SwiftUI property wrappers explained and compared

이찬희 (MarkiiimarK)
Never Stop Learning.