Bouncy Image Picker
An animated image picker with bouncy spring animations and haptic feedback
Preview
Code
1import SwiftUI2import PhotosUI34struct BounceImagePicker: View {5 @State private var selectedItem: PhotosPickerItem?6 @State private var selectedImage: UIImage?7 @State private var isImageVisible = false8 @State private var imageScale: CGFloat = 0.59 @State private var imageRotation: Double = -1510 @State private var imageOffset: CGFloat = UIScreen.main.bounds.height11 12 // Haptic engine13 let impactHaptic = UIImpactFeedbackGenerator(style: .medium)14 15 var body: some View {16 ZStack {17 // Background18 Color(UIColor.systemGray6)19 .ignoresSafeArea()20 21 // Selected Image22 if let selectedImage {23 Image(uiImage: selectedImage)24 .resizable()25 .aspectRatio(contentMode: .fit)26 .frame(maxWidth: UIScreen.main.bounds.width * 0.8)27 .padding(12)28 .background(.white)29 .clipShape(RoundedRectangle(cornerRadius: 16))30 .shadow(color: .black.opacity(0.2), radius: 15, x: 0, y: 5)31 .scaleEffect(isImageVisible ? 1 : imageScale)32 .rotationEffect(.degrees(isImageVisible ? 0 : imageRotation))33 .offset(y: isImageVisible ? 0 : imageOffset)34 .animation(.spring(response: 0.6, dampingFraction: 0.6, blendDuration: 0.6), value: isImageVisible)35 }36 37 // Bottom Button38 VStack {39 Spacer()40 41 PhotosPicker(selection: $selectedItem,42 matching: .images,43 photoLibrary: .shared()) {44 Text("Add Image")45 .font(.system(size: 22, weight: .semibold, design: .rounded))46 .foregroundColor(.black)47 .frame(maxWidth: .infinity)48 .padding(.horizontal, 32)49 .padding(.vertical, 20)50 .background(51 Capsule()52 .fill(.white)53 .shadow(color: .black.opacity(0.05), radius: 8, x: 0, y: 4)54 )55 .overlay(56 Capsule()57 .stroke(Color(UIColor.systemGray4), lineWidth: 1)58 )59 .padding(.horizontal, 24)60 .padding(.bottom, 32)61 }62 .onChange(of: selectedItem) { oldValue, newValue in63 Task {64 if let data = try? await newValue?.loadTransferable(type: Data.self),65 let image = UIImage(data: data) {66 selectedImage = image67 isImageVisible = false68 69 // Reset animation states70 imageScale = 0.571 imageRotation = -1572 imageOffset = UIScreen.main.bounds.height73 74 // Trigger haptic75 impactHaptic.impactOccurred(intensity: 0.8)76 77 // Start animation after a brief delay78 try? await Task.sleep(nanoseconds: 200_000_000) // 0.2s79 withAnimation {80 isImageVisible = true81 }82 }83 }84 }85 }86 }87 }88}8990struct BounceImagePickerDemoView: View {91 var body: some View {92 BounceImagePicker()93 .navigationTitle("Bounce Image Picker")94 }95}9697#Preview {98 NavigationView {99 BounceImagePickerDemoView()100 }101}