0

SwiftUI has the default slide transition .transition(.slide). It works well, but only halfway. I mean, I have to slide my views and then slide them back (I need the animation backwards). Since it is not possible to set the direction of the slide transition, I implemented a custom transition. The implementation is simple enough and works well. But I can't get the view size to calculate the start and end offset points.

extension AnyTransition {
    static func slide(direction: SlideModifier.Direction) -> AnyTransition {
        .asymmetric(insertion: .modifier(active: SlideModifier(positiveOffset: true, direction: direction), identity: SlideModifier(positiveOffset: nil, direction: direction)),
                    removal: .modifier(active: SlideModifier(positiveOffset: false, direction: direction), identity: SlideModifier(positiveOffset: nil, direction: direction)))
    }
}

internal struct SlideModifier: ViewModifier {
    let positiveOffset: Bool?
    let direction: Direction
    let offsetSize: CGFloat = 200 // How can I get this value programmatically?
    
    enum Direction {
        case leading, top, trailing, bottom
    }
    
    func body(content: Content) -> some View {
        switch direction {
        case .leading:
            return content.offset(x: positiveOffset == nil ? 0 : (positiveOffset! ? offsetSize : -offsetSize))
        case .top:
            return content.offset(y: positiveOffset == nil ? 0 : (positiveOffset! ? offsetSize : -offsetSize))
        case .trailing:
            return content.offset(x: positiveOffset == nil ? 0 : (positiveOffset! ? -offsetSize : offsetSize))
        case .bottom:
            return content.offset(y: positiveOffset == nil ? 0 : (positiveOffset! ? -offsetSize : offsetSize))
        }
    }
}

I want to define the offset size that is equal to the view size. My experiments have shown that this is how the standard slide transition works. I tried using GeometryReader and PreferenceKey to get the size. Maybe I did something wrong, but it didn't work.

Here's a simple example of using my custom transition.

struct ContentView: View {
    @State private var isShowingRed = false

    var body: some View {
        ZStack {
            if isShowingRed {
                Color.red
                    .frame(width: 200, height: 200)
                    .transition(.slide(direction: .trailing))
            } else {
                Color.blue
                    .frame(width: 200, height: 200)
                    .transition(.slide(direction: .leading))
            }
        }
        .onTapGesture {
            withAnimation {
                isShowingRed.toggle()
            }
        }
    }
}

enter image description here

Iaenhaall
  • 404
  • 5
  • 9
  • `UIScreen.main.bounds.someProperty` will get you your screen size without a GeometryReader. Also you should be able to set an offset on that by doing something like `View().offset(x: isOffScreen == true ? UIScreen.main.bounds.size.width / 2 : 0)` Then when you change `isOffScreen` wrap it in `withAnimation { isOffScreen.toggle() } ` – xTwisteDx Apr 16 '21 at 18:09
  • 1
    @laenhall: the way that you are trying to solve the issue is wrong! you do not need read the size in each animation, it is abuse of cpu, you just need read once per lunch and update per orientation change – ios coder Apr 16 '21 at 18:31
  • @xTwisteDx If I use `UIScreen.main.bounds`, the animated objects will move across the whole screen. This behavior is not always acceptable. – Iaenhaall Apr 23 '21 at 17:04
  • @swiftPunk Do you have a suggestion on how to do this? – Iaenhaall Apr 23 '21 at 17:05

1 Answers1

0

There are a few ways to get the size of a view. In your case I'm assuming you're looking for the screen size. Here are the methods to get that. Please bear in mind I don't have my IDE open to fully test this, there may be syntax issues in it. I'll update my answer later when I have my IDE open.

Hot Tip

Get used to using an in-line condition, it'll help tremendously when dealing with animations and views in Swift UI

someBool ? varIfTrue : varIfFalse
someValue == someTest ? varIfTrue : varIfFalse
//(boolCondition) ? (Value if True) : (Value If False)

//You can even chain them together, but be careful to keep it 
//organized if you do this.
someBool ? someBool2 ? val1 : val2 : someBool2 ? val3 : val4

Raw Screen Size

Nice Extension Provided Here

var width = UIScreen.main.bounds.size.width
var height = UIScreen.main.bounds.size.height

Geometry Reader

This one is a bit more confusing because it will get the VIEW that it is in. It also has a property that you can set to ignore safe areas. If you use this method you'll get different responses based on the view.

//Parent Views Matter with GeometryReader
var body: some View {
    GeometryReader { geometry in 
        //Use the geometry, full screen size.
    }.ignoresSafeAreaAndEdges(.all)
}

var body: someView {
    VStack {
        GeometryReader { geometry in
             //This will have only the size of the parent view.
             // Ex: 100x100 square.
        }
    }.frame(width: 100, height: 100)
}

Animating Backwards

The way that you're animating works, however doing it this way will give you a "Rewind" type effect.

var body: some View {
    ZStack {
        if isShowingRed {
            Color.red
                .frame(width: 200, height: 200)
                .offset(isShowingRed ? 0 : 100)
                .animation(.spring()) //You can also use .default
        } else {
            Color.blue
                .frame(width: 200, height: 200)
                .offset(isShowingRed ? 100 : 0)
                .animation(.spring()) //You can also use .default

        }
    }
    .onTapGesture {
        withAnimation {
            isShowingRed.toggle()
        }
    }
}
xTwisteDx
  • 2,152
  • 1
  • 9
  • 25