I am trying to achieve an zoom out Animation is Swift UI. But the animation I am getting is not the intended one.
I want an animation that feels the ImageCarousel
getting zoomed out to URLImageView
and the titleButtonView moving up.
Below is the sample Code
struct ImageAndTitleView: View {
@State private var isVStack: Bool = true
// MARK: Private Variables
private struct Defaults {
static let bigImageSize: CGFloat = 216
static let smallImageSize: CGFloat = 80
static let spacing: CGFloat = 8.0
static let imageSpacing: CGFloat = 12.0
static let image: String = "image"
static let title: String = "tittle"
static let subtitle: String = "subtitle"
static let button: String = "button"
static let lineLimit: Int = 3
static let cornerRadius: CGFloat = 8.0
static let borderWidth: CGFloat = 1.0
static let padding: CGFloat = 16.0
}
// MARK: - Body
let images = [URL(string: "https://picsum.photos/400/200")!, URL(string: "https://picsum.photos/400/200")!, URL(string: "https://picsum.photos/400/200")!] // Replace with actual image names
let title = "Its can be a 3 lines long title"
let subtitle = "the subtitle goes here"
@Namespace private var animation
var body: some View {
VStack {
if isVStack {
VStack(alignment: .leading, spacing: Defaults.spacing) {
ImageCarousel(images: images, size: Defaults.bigImageSize)
.frame(width: isVStack ? UIScreen.main.bounds.width - 32 : Defaults.smallImageSize, height: Defaults.bigImageSize)
.matchedGeometryEffect(id: Defaults.image, in: animation)
TitleAndButtonView
}
} else {
HStack {
VStack(alignment: .leading, spacing: Defaults.spacing) {
TitleAndButtonView
}
.padding(8)
URLImageView(url: URL(string: "https://picsum.photos/100")!)
.frame(width: Defaults.smallImageSize, height:Defaults.smallImageSize)
.padding(Defaults.spacing)
.background(Color.white)
.cornerRadius(Defaults.cornerRadius)
.matchedGeometryEffect(id: Defaults.image, in: animation)
.overlay(
RoundedRectangle(cornerRadius: Defaults.cornerRadius)
.stroke(Color.blue, lineWidth: Defaults.borderWidth)
)
}
}
}
.padding()
.onAppear {
startLayoutChangeTimer()
}
}
private func startLayoutChangeTimer() {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
withAnimation(.easeInOut(duration: 0.45).delay(0.1)) {
withAnimation(.spring()) {
isVStack = false
}
}
}
}
var TitleAndButtonView: some View {
VStack(alignment: .leading, spacing: Defaults.spacing) {
Text(title)
.font(.headline)
.multilineTextAlignment(.leading)
.lineLimit(Defaults.lineLimit)
.matchedGeometryEffect(id: Defaults.title, in: animation)
Text(subtitle)
.font(.subheadline)
.matchedGeometryEffect(id: Defaults.subtitle, in: animation)
Button(action: {
// Button action
}) {
Text("Button")
}
.matchedGeometryEffect(id: Defaults.button, in: animation)
}
}
struct ImageCarousel: View {
let images: [URL]
let size: CGFloat
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: Defaults.imageSpacing) {
ForEach(0..<images.count, id: .self) { index in
URLImageView(url: images[index])
.frame(width: size, height: size)
.padding(Defaults.spacing)
.background(Color.white)
.cornerRadius(Defaults.cornerRadius)
.overlay(
RoundedRectangle(cornerRadius: Defaults.cornerRadius)
.stroke(Color.blue, lineWidth: Defaults.borderWidth)
)
.padding(.trailing, index < images.count - 1 ? 0 : Defaults.imageSpacing) // Adjust padding to show part of the next image
}
}
// .padding(.leading, 10) // Padding at the beginning
}
.frame(height: size + Defaults.padding)
}
}
struct URLImageView: View {
let url: URL
@State private var image: UIImage? = nil
var body: some View {
ZStack {
if let uiImage = image {
Image(uiImage: uiImage)
.resizable()
.scaledToFit()
.clipped()
} else {
Color.gray
.onAppear {
loadImage()
}
}
}
}
private func loadImage() {
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data, let uiImage = UIImage(data: data) {
DispatchQueue.main.async {
self.image = uiImage
}
}
}.resume()
}
}
}
any help is highly appreciated.