Home > Net >  Flipping between views using .rotation3DEffect in SwiftUI
Flipping between views using .rotation3DEffect in SwiftUI

Time:09-23

I'm new to coding having done very little of it previously, and am yet again trying to learn how to code using Swift / SwiftUI.

Although I do have a simple app idea that I don't expect will ever make any money but it will fill a particular need of mine and hopefully others in my position.

For the UI of my app I'd like to display different views depending on what the user wants to do and I have the idea to present these views in a circular design and "flip" the circle over as each view is required.

I've looked at a few different "flip animation" tutorials and they all seem to focus on just two views, and flipping between them (ie like a playing card) but what I need is to have an arbitrary number of views and flip between any given two.

I thought I had it with my last attempt but the results are unexpected (to me) and I'm struggling to follow why. Which is probably my fault, as I used a function from one of the tutorials that flips between just two views. Here is my (admittedly messy I know) code. I know there will be tons of things I've done wrong, and better ways of doing things so please don't be too hard on me :)


struct tealView : View {
    
    @Binding var degree : Double
    
    var body: some View {
        
        ZStack{
            Group {
                Circle()
                    .frame(width: 350, height: 350)
                    .foregroundColor(Color(.systemTeal))
                Text("Teal View!")
            }
        }.rotation3DEffect(Angle(degrees: degree), axis: (x: 0, y: 1, z: 0), anchor: .center, anchorZ: 0, perspective: 0.2)
    }
}

struct redView : View {
    
    @Binding var degree : Double
    
    var body: some View {
        
        ZStack {
            Group {
                Circle()
                    .frame(width: 350, height: 350)
                    .foregroundColor(Color(.systemRed))
                Text("Red View!")
            }
        }.rotation3DEffect(Angle(degrees: degree), axis: (x: 0, y: 1, z: 0), anchor: .center, anchorZ: 0, perspective: 0.2)
    }
}

struct blueView : View {
    
    @Binding var degree : Double
    
    var body: some View {
        
        ZStack {
            Group {
                Circle()
                    .frame(width: 350, height: 350)
                    .foregroundColor(Color(.systemBlue))
                Text("Blue View!")
            }
        }.rotation3DEffect(Angle(degrees: degree), axis: (x: 0, y: 1, z: 0), anchor: .center, anchorZ: 0, perspective: 0.2)
    }
}

struct yellowView : View {
    
    @Binding var degree : Double
    
    var body: some View {
        
        ZStack {
            Group {
                Circle()
                    .frame(width: 350, height: 350)
                    .foregroundColor(Color(.systemYellow))
                Text("Yellow View!")
            }
        }.rotation3DEffect(Angle(degrees: degree), axis: (x: 0, y: 1, z: 0), anchor: .center, anchorZ: 0, perspective: 0.2)
    }
}

struct ContentView: View {
    @State var backDegree = 0.0
    @State var frontDegree = -90.0
    
    @State var isFlipped = false
    
    @State private var tealFront = true
    @State private var redFront = false
    @State private var blueFront = false
    @State private var yellowFront = false
    
    @State private var tealWasFront = false
    @State private var redWasFront = true
    @State private var blueWasFront = false
    @State private var yellowWasFront = false
    
    let durationAndDelay : CGFloat = 0.45
    
    
    
    func flipcard () {
        isFlipped = !isFlipped
        if isFlipped {
            withAnimation(.easeInOut(duration: durationAndDelay)) {
                backDegree = 90
            }
            withAnimation(.easeInOut(duration: durationAndDelay).delay(durationAndDelay)){
                frontDegree = 0
            }
        } else {
            withAnimation(.easeInOut(duration: durationAndDelay)) {
               frontDegree = -90
            }
            withAnimation(.easeInOut(duration: durationAndDelay).delay(durationAndDelay)){
               backDegree = 0
            }
       }
        
    }
    
    var body: some View {
        VStack {
            ZStack {
                if tealFront {
                    tealView(degree: $frontDegree)
                    if yellowWasFront {
                        yellowView(degree: $backDegree)
                    } else if redWasFront {
                        redView(degree: $backDegree)
                    } else {
                        blueView(degree: $backDegree)
                    }
                } else if redFront {
                    redView(degree: $frontDegree)
                    if yellowWasFront {
                        yellowView(degree: $backDegree)
                    } else if tealWasFront {
                        tealView(degree: $backDegree)
                    } else {
                        blueView(degree: $backDegree)
                    }
                } else if blueFront {
                    blueView(degree: $frontDegree)
                    if yellowWasFront {
                        yellowView(degree: $backDegree)
                    } else if redWasFront {
                        redView(degree: $backDegree)
                    } else {
                        tealView(degree: $backDegree)
                    }
                } else {
                    yellowView(degree: $frontDegree)
                    if tealWasFront {
                        tealView(degree: $backDegree)
                    } else if redWasFront {
                        redView(degree: $backDegree)
                    } else {
                        blueView(degree: $backDegree)
                    }
                }
            }
            .padding(20)
            HStack {
                Group {
                    Button {
                        if !tealFront { tealFront = true }
                        if redFront {
                            redFront = false
                            redWasFront = true
                            blueWasFront = false
                            yellowWasFront = false
                            tealWasFront = false
                        }
                        if blueFront { blueFront = false
                            blueWasFront = true
                            redWasFront = false
                            yellowWasFront = false
                            tealWasFront = false
                        }
                        if yellowFront { yellowFront = false
                            yellowWasFront = true
                            blueWasFront = false
                            redWasFront = false
                            tealWasFront = false
                        }
                        
                        flipcard()
                    } label: {
                        ZStack {
                            Circle()
                                .foregroundColor(Color(.systemTeal))
                            Text("Teal")
                                .foregroundColor(Color.black)
                        }
                    }
                }
                
                Group {
                    Button {
                        if !redFront { redFront = true }
                        if tealFront {
                            tealFront = false
                            tealWasFront = true
                            blueWasFront = false
                            yellowWasFront = false
                            redWasFront = false
                        }
                        if blueFront { blueFront = false
                            blueWasFront = true
                            yellowWasFront = false
                            tealWasFront = false
                            redWasFront = false
                        }
                        if yellowFront { yellowFront = false
                            yellowWasFront = true
                            blueWasFront = false
                            tealWasFront = false
                            redWasFront = false
                        }
                        
                        flipcard()
                    } label: {
                        ZStack {
                            Circle()
                                .foregroundColor(Color(.systemRed))
                            Text("Red")
                                .foregroundColor(Color.black)
                        }
                    }
                }
                
                Group {
                    Button {
                        if !blueFront { blueFront = true }
                        if redFront {
                            redFront = false
                            redWasFront = true
                            tealWasFront = false
                            yellowWasFront = false
                            blueWasFront = false
                        }
                        if tealFront { tealFront = false
                            tealWasFront = true
                            redWasFront = false
                            yellowWasFront = false
                            blueWasFront = false
                        }
                        if yellowFront { yellowFront = false
                            yellowWasFront = true
                            tealWasFront = false
                            redWasFront = false
                            blueWasFront = false
                        }
                       
                        flipcard()
                    } label: {
                        ZStack {
                            Circle()
                                .foregroundColor(Color(.systemBlue))
                            Text("Blue")
                                .foregroundColor(Color.black)
                        }
                    }
                }
                
                Group {
                    Button {
                        if !yellowFront { yellowFront = true }
                        if redFront {
                            redFront = false
                            redWasFront = true
                            blueWasFront = false
                            tealWasFront = false
                            yellowWasFront = false
                        }
                        if blueFront { blueFront = false
                            blueWasFront = true
                            redWasFront = false
                            tealWasFront = false
                            yellowWasFront = false
                        }
                        if tealFront { tealFront = false
                            tealWasFront = true
                            blueWasFront = false
                            redWasFront = false
                            yellowWasFront = false
                        }
                       
                        flipcard()
                    } label: {
                        ZStack {
                            Circle()
                                .foregroundColor(Color(.systemYellow))
                            Text("Yellow")
                                .foregroundColor(Color.black)
                        }
                    }
                }
            }
        }
    }
}





struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

CodePudding user response:

There is quite a bit a redundancy (still) in this code, but I simplified it a bit and got it working. I replaced your Bools with frontView and backView enums that take ViewColors.

When a color is clicked: Only flip when the color we want is not showing. Since frontView and backView are reversed if isFlipped is true, you have to handle that extra bit of logic. In either case, replace the hidden view with the color we want, and then flip.

Note: When the app starts, we are looking at the backView of an unflipped View, which is confusing. I think this is because the original flipping code may have been based upon cards that are dealt face down. The code makes sense if you think "flipped == true" means frontView is showing, "flipped == false" means backView is showing.

enum ViewColors {
    case teal, red, blue, yellow
}

struct ContentView: View {
    @State var backDegree = 0.0
    @State var frontDegree = -90.0
    
    @State var isFlipped = false
    
    @State private var frontView = ViewColors.teal
    @State private var backView = ViewColors.red
    
    let durationAndDelay : CGFloat = 0.45
        
    func flipcard () {
        isFlipped.toggle()
        if isFlipped {
            withAnimation(.easeInOut(duration: durationAndDelay)) {
                backDegree = 90
            }
            withAnimation(.easeInOut(duration: durationAndDelay).delay(durationAndDelay)){
                frontDegree = 0
            }
        } else {
            withAnimation(.easeInOut(duration: durationAndDelay)) {
               frontDegree = -90
            }
            withAnimation(.easeInOut(duration: durationAndDelay).delay(durationAndDelay)){
               backDegree = 0
            }
       }
        
    }
    
    var body: some View {
        VStack {
            ZStack {
                VStack {
                    switch backView {
                    case .teal:
                        tealView(degree: $backDegree)
                    case .red:
                        redView(degree: $backDegree)
                    case .blue:
                        blueView(degree: $backDegree)
                    case .yellow:
                        yellowView(degree: $backDegree)
                    }
                }
                
                VStack {
                    switch frontView {
                    case .teal:
                        tealView(degree: $frontDegree)
                    case .red:
                        redView(degree: $frontDegree)
                    case .blue:
                        blueView(degree: $frontDegree)
                    case .yellow:
                        yellowView(degree: $frontDegree)
                    }
                }
                
            }
            .padding(20)
            HStack {
                Group {
                    Button {
                        if !isFlipped {
                            if backView != .teal {
                                frontView = .teal
                                flipcard()
                            }
                        } else {
                            if frontView != .teal {
                                backView = .teal
                                flipcard()
                            }
                        }
                        
                    } label: {
                        ZStack {
                            Circle()
                                .foregroundColor(Color(.systemTeal))
                            Text("Teal")
                                .foregroundColor(Color.black)
                        }
                    }
                }
                
                Group {
                    Button {
                        if !isFlipped {
                            if backView != .red {
                                frontView = .red
                                flipcard()
                            }
                        } else {
                            if frontView != .red {
                                backView = .red
                                flipcard()
                            }
                        }
                        
                    } label: {
                        ZStack {
                            Circle()
                                .foregroundColor(Color(.systemRed))
                            Text("Red")
                                .foregroundColor(Color.black)
                        }
                    }
                }
                
                Group {
                    Button {
                        if !isFlipped {
                            if backView != .blue {
                                frontView = .blue
                                flipcard()
                            }
                        } else {
                            if frontView != .blue {
                                backView = .blue
                                flipcard()
                            }
                        }
                       
                    } label: {
                        ZStack {
                            Circle()
                                .foregroundColor(Color(.systemBlue))
                            Text("Blue")
                                .foregroundColor(Color.black)
                        }
                    }
                }
                
                Group {
                    Button {
                        if !isFlipped {
                            if backView != .yellow {
                                frontView = .yellow
                                flipcard()
                            }
                        } else {
                            if frontView != .yellow {
                                backView = .yellow
                                flipcard()
                            }
                        }
                       
                    } label: {
                        ZStack {
                            Circle()
                                .foregroundColor(Color(.systemYellow))
                            Text("Yellow")
                                .foregroundColor(Color.black)
                        }
                    }
                }
            }
        }
    }
}
  • Related