skip to Main Content

How to achieve the Rotary type Carousel present in the iCarousel Framework of swift. The following is what I wanted to achieve with the SwiftUI

enter image description here

I checked many tutorials and the framework present but I could not able to achieve as shown above image

2

Answers


  1. Here is a general approach: All items are drawn above each other in a ZStack, then their position and opacity is changed based on the "distance" to the foremost element.

    The demo has a fixed size for the item, but can easily be adapted. Change the values in opacity and scaleEffectto your wishes.

    enter image description here

    struct Item: Identifiable {
        var id: Int
        var title: String
        var color: Color
    }
    
    class Store: ObservableObject {
        @Published var items: [Item]
        
        let colors: [Color] = [.red, .orange, .blue, .teal, .mint, .green, .gray, .indigo, .black]
    
        // dummy data
        init() {
            items = []
            for i in 0...7 {
                let new = Item(id: i, title: "Item (i)", color: colors[i])
                items.append(new)
            }
        }
    }
    
    
    struct ContentView: View {
        
        @StateObject var store = Store()
        @State private var snappedItem = 0.0
        @State private var draggingItem = 0.0
        
        var body: some View {
            
            ZStack {
                ForEach(store.items) { item in
                    
                    // article view
                    ZStack {
                        RoundedRectangle(cornerRadius: 18)
                            .fill(item.color)
                        Text(item.title)
                            .padding()
                    }
                    .frame(width: 200, height: 200)
                    
                    .scaleEffect(1.0 - abs(distance(item.id)) * 0.2 )
                    .opacity(1.0 - abs(distance(item.id)) * 0.3 )
                    .offset(x: myXOffset(item.id), y: 0)
                    .zIndex(1.0 - abs(distance(item.id)) * 0.1)
                }
            }
            .gesture(
                DragGesture()
                    .onChanged { value in
                        draggingItem = snappedItem + value.translation.width / 100
                    }
                    .onEnded { value in
                        withAnimation {
                            draggingItem = snappedItem + value.predictedEndTranslation.width / 100
                            draggingItem = round(draggingItem).remainder(dividingBy: Double(store.items.count))
                            snappedItem = draggingItem
                        }
                    }
            )
        }
        
        func distance(_ item: Int) -> Double {
            return (draggingItem - Double(item)).remainder(dividingBy: Double(store.items.count))
        }
        
        func myXOffset(_ item: Int) -> Double {
            let angle = Double.pi * 2 / Double(store.items.count) * distance(item)
            return sin(angle) * 200
        }
        
    }
    
    Login or Signup to reply.
  2. Thank you @ChrisR this is a great way to achieve Carousel experience.

    Added active index in the @ChrisR‘s answer, that might be useful for someone.

    @ChrisR once you add active index in your answer, I can remove my post.

    import SwiftUI
    
    struct Item: Identifiable {
        var id: Int
        var title: String
        var color: Color
    }
    
    class Store: ObservableObject {
        @Published var items: [Item]
        
        let colors: [Color] = [.red, .orange, .blue, .teal, .mint, .green, .gray, .indigo, .black]
        
        // dummy data
        init() {
            items = []
            for i in 0...7 {
                let new = Item(id: i, title: "Item (i)", color: colors[i])
                items.append(new)
            }
        }
    }
    
    struct ContentView: View {
        
        @StateObject var store = Store()
        @State private var snappedItem = 0.0
        @State private var draggingItem = 0.0
        @State var activeIndex: Int = 0
        
        var body: some View {
            
            ZStack {
                ForEach(store.items) { item in
                    
                    // article view
                    ZStack {
                        RoundedRectangle(cornerRadius: 18)
                            .fill(item.color)
                        Text(item.title)
                            .padding()
                    }
                    .frame(width: 200, height: 200)
                    
                    .scaleEffect(1.0 - abs(distance(item.id)) * 0.2 )
                    .opacity(1.0 - abs(distance(item.id)) * 0.3 )
                    .offset(x: myXOffset(item.id), y: 0)
                    .zIndex(1.0 - abs(distance(item.id)) * 0.1)
                }
            }
            .gesture(
                DragGesture()
                    .onChanged { value in
                        draggingItem = snappedItem + value.translation.width / 100
                    }
                    .onEnded { value in
                        withAnimation {
                            draggingItem = snappedItem + value.predictedEndTranslation.width / 100
                            draggingItem = round(draggingItem).remainder(dividingBy: Double(store.items.count))
                            snappedItem = draggingItem
                            
                            //Get the active Item index
                            self.activeIndex = store.items.count + Int(draggingItem)
                            if self.activeIndex > store.items.count || Int(draggingItem) >= 0 {
                                self.activeIndex = Int(draggingItem)
                            }
                            print(self.activeIndex)
                        }
                    }
            )
        }
        
        func distance(_ item: Int) -> Double {
            return (draggingItem - Double(item)).remainder(dividingBy: Double(store.items.count))
        }
        
        func myXOffset(_ item: Int) -> Double {
            let angle = Double.pi * 2 / Double(store.items.count) * distance(item)
            return sin(angle) * 200
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search