Index unexpectedly changes when trying to make Swipeable Image View with SwiftUI

Viewed 113

I am trying to make Swipeable Image View with SwiftUI. Swiping left/right moves image left/right, and when you swipe long enough, the image slides away and next/previous image will appear: Just like home screen tabs. Tapping left/right makes previous/next image replace the current image, but without animation.

This view gets array of images, index and image offset.

Some other view detects swipe gesture and changes index. The problem is, when swipe gesture changes index and image slides, the index changed right after animation started, not after animation is complete.

It seems like var currentIndex = index is the origin of this bug, bug how can I fix it?

struct MyImagesView: View {
    @Binding var index:Int
    let imgArray:[URL]
    @Binding var dragOffset:CGSize
    @State var nextindex:Int = 1
    @State var screenSize:CGRect = UIScreen.main.bounds
    var body: some View {
        var currentindex = index
        let insertionAnimation: ()->AnyTransition = { nextindex > currentindex ? AnyTransition.move(edge: .leading) : AnyTransition.move(edge: .trailing) }
        let removalAnimation: ()->AnyTransition = { nextindex < currentindex ? AnyTransition.move(edge: .trailing) : AnyTransition.move(edge: .leading) }
        ZStack {`enter code here`
            MyImageView(array: imgArray,index:currentindex-2).offset(x: -screenSize.width, y: 0)
            MyImageView(array: imgArray,index:currentindex-1)
            MyImageView(array: imgArray,index:currentindex).offset(x: screenSize.width, y: 0)
        }
        .offset(dragOffset)
        .onAppear(perform: { nextindex = index })
        .onChange(of: index, perform: { value in
            if dragOffset.width != 0 && value != currentindex {
                nextindex = value
                Timer.scheduledTimer(withTimeInterval: 0.5, repeats: false, block: { _ in dragOffset = .zero; currentindex = value })
                dragOffset.width = nextindex>currentindex ? -screenSize.width : (nextindex < currentindex ? screenSize.width : 0)
            }
        })
        .transition(.asymmetric(insertion: insertionAnimation(), removal: removalAnimation()))
        .animation(.linear(duration: (dragOffset.width==0) ? 0 : 0.5))
    }
}
1 Answers

I used Timer to change index after animation duration(=0.5s) and that solved the problem. And there was no need for transition.

struct MyImagesView: View {
    @Binding var index:Int
    let imgArray:[URL]
    @Binding var dragOffset:CGSize
    @State var nextindex:Int = 1
    @State var screenSize:CGRect = UIScreen.main.bounds
    var body: some View {
        ZStack {
            MyImageView(array: imgArray, index: index-2).offset(x: -screenSize.width, y: 0)
            MyImageView(array: imgArray, index: index-1)
            MyImageView(array: imgArray, index: index).offset(x: screenSize.width, y: 0)
        }
        .offset(dragOffset)
        .onAppear(perform: { nextindex = index })
        .onChange(of: index, perform: { value in
            if dragOffset.width != 0 {
                dragOffset = .zero
            }
        })
        .animation(.linear(duration: (dragOffset.width==0) ? 0 : 0.5))
    }
}
Related