Hugues
Hugues

Reputation: 164

SwiftUI - animating a new image inside the current view

I have a View where I use a Picture(image) subview to display an image, which can come in different height and width formats.

The reference to the image is extracted from an array, which allows me to display different images in my View, by varying the reference. SwiftUI rearrange the content of view for each new image

I would like an animation on this image, say a scale effect, when the image is displayed

1) I need a first .animation(nil) to avoid animating the former image (otherwise I have an ugly fade out and aspect ratio deformation). Seems the good fix

2) But then I have a problem with the scaleEffect modifier (even if I put it to scale = 1, where it should do nothing) The animation moves from image 1 to image 2 by imposing that the top left corner of image 2 starts from the position of top left corner of image 1, which, with different widths and heights, provokes a unwanted translation of the image center
This is reproduced in the code below where for demo purposes I'm using system images (which are not prone to bug 1))

How can I avoid that ?

3) In the demo code below, I trigger the new image with a button, which allows me to use an action and handle "scale" modification and achieve explicitly the desired effect. However in my real code, the image modification is triggered by another change in another view.

Swift knows that, hence I can use an implicit .animation modifier.

However, I can't figure out how to impose a reset of "scale" for any new image and perform my desired effect.

Any idea how to achieve the action in the below code in the Button on an implicit animation ?

Thanks

import SwiftUI

let portrait = Image(systemName: "square.fill")
let landscape = Image(systemName: "square.fill")

struct ContentView: View {
    @State var modified = false
    @State var scale: CGFloat = 1

var body: some View {
    return VStack(alignment: .center) {

        Pictureclip(bool: $modified)  
            .animation(nil)
            .scaleEffect(scale)
            .animation(.easeInOut(duration: 1))

       Button(action: {
        self.modified.toggle()
        self.scale = 1.1
        DispatchQueue.main.asyncAfter(deadline: .now() + 1)
            {self.scale = 1}
        }) {
            Text("Tap here")
                .animation(.linear)
        }            
     }
}
}
struct Pictureclip: View {

  @Binding var bool: Bool

  var body: some View {
  if bool == true {
    return  portrait
        .resizable()
        .frame(width: 100, height: 150)
        .foregroundColor(.green)
  } else {
   return  landscape
        .resizable()
        .frame(width: 150, height: 100)
        .foregroundColor(.red)
    }
    }
}

Upvotes: 1

Views: 1310

Answers (2)

Eldar
Eldar

Reputation: 554

I am not sure about it, but maybe it can be helpful for you. Use DataBinding structure. I use it like this:

let binding = Binding<String>(get: {
            self.storage
        }, set: { newValue in
            self.textOfPrimeNumber = ""
            self.storage = newValue
            let _ = primeFactorization(n: Int(self.storage)!, k: 2, changeable: &self.textOfPrimeNumber)
        })

Upvotes: 0

Hugues
Hugues

Reputation: 164

I have a semi answer to my question, namely points 1 & 2 (here with reference to two jpeg images in the asset catalog)

import SwiftUI
let portrait = Image("head")
let landscape = Image("sea")

struct ContentView: View {
    @State var modified = false
    @State var scale: CGFloat = 0.95


var body: some View {
    VStack(){

    GeometryReader { geo in
        VStack {
            Picture(bool: self.modified)
                .frame(width: geo.size.width * self.scale)
        }
        }
      Spacer()
      Button(action: {
                self.scale = 0.95
                self.modified.toggle()

        withAnimation(.easeInOut(duration: 0.5)){
                    self.scale = 1

                }
      }) {
                Text("Tap here")
          }
    }
}
}
struct Picture: View {
    var bool: Bool
var body: some View {
if bool == true {
    return  portrait
        .resizable().aspectRatio(contentMode: .fit)
        .padding(.all,6.0)
} else {
   return  landscape
        .resizable().aspectRatio(contentMode: .fit)
        .padding(.all,6.0)
}
}
}

This solution enables scaling without distorting the aspect ratio of the new image during the animation. But It does not work in a code where the image update is triggered in another view. I guess I have to restructure my code, either to solve my problem or to expose it more clearly.

Edit: a quick and dirty solution is to put the triggering code (here the action code in the button) in the other view. Namely, put in view B the code that animates view A, with a state variable passed to it (here, "scale"). I'm sure there are cleaner ways, but at least this works.

Upvotes: 0

Related Questions