KVHeroTransition is a lightweight, customizable, and elegant transition animation library for iOS applications. It provides two main transition styles: Hero-style zoom transitions and Pinterest-style transitions.
ðŊ Perfect for: Photo galleries, detail views, card-based UIs, and any app requiring smooth visual transitions
- âĻ Two transition styles:
- Hero-style zoom transitions
- Pinterest-style transitions
- ð Interactive drag-to-dismiss gesture support
- ðĻ Customizable corner radius and curve animations
- ðŠķ Ultra-lightweight - zero third-party dependencies
- ð Easy integration - just a few lines of code to get started
- ðą iOS 13+ compatible with Swift 5.3+
- ð§ Fully customizable animation parameters
- ðŊ 100% Swift implementation
Smooth transitions with interactive dismiss gestures
Add to your Podfile
:
pod 'KVHeroTransition'
Then run:
pod install
.package(url: "https://github.com/khanhVu-ops/KVHeroTransition.git", from: "1.0.0")
Step 1: Make your view controllers conform to KVTransitionAnimatable
import KVHeroTransition
class PhotoDetailViewController: UIViewController, KVTransitionAnimatable {
var photo: UIImage!
private let imageView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFit
iv.translatesAutoresizingMaskIntoConstraints = false
return iv
}()
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
private func setupUI() {
view.backgroundColor = .systemBackground
view.addSubview(imageView)
imageView.image = photo
NSLayoutConstraint.activate([
imageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
imageView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
imageView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
])
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap))
view.addGestureRecognizer(tapGesture)
}
@objc private func handleTap() {
dismiss(animated: true)
}
// MARK: - KVTransitionAnimatable
var imageViewFrame: CGRect? {
return imageView.frame
}
var cornerRadius: CGFloat {
return 12
}
func heroImage() -> UIImage? {
return imageView.image
}
func heroImageContentMode() -> UIView.ContentMode {
return .scaleAspectFill
}
func animationWillStart(transitionType: KVTransitionType) {
// Handle animation start
}
func animationDidEnd(transitionType: KVTransitionType) {
// Handle animation end
}
}
Step 2: Create a source view controller with collection view
class HeroTransitionDemoViewController: UIViewController {
private let collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 10
layout.minimumInteritemSpacing = 10
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.backgroundColor = .systemBackground
cv.register(PhotoCell.self, forCellWithReuseIdentifier: "PhotoCell")
return cv
}()
// Important: Store transition manager as a strong reference
private var transitionManager: Any?
private var cellSelected: PhotoCell?
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
private func setupUI() {
// Setup collection view
view.addSubview(collectionView)
collectionView.delegate = self
collectionView.dataSource = self
// ... setup constraints
}
}
// MARK: - UICollectionViewDelegate
extension HeroTransitionDemoViewController: UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
guard let cell = collectionView.cellForItem(at: indexPath) as? PhotoCell else { return }
cellSelected = cell
let detailVC = PhotoDetailViewController()
detailVC.photo = photos[indexPath.item]
// Create and store transition manager
let heroManager = KVHeroTransitionManager(
presentingViewController: self,
presentedViewController: detailVC
)
// Important: Keep a strong reference to the transition manager
transitionManager = heroManager
detailVC.modalPresentationStyle = .custom
detailVC.transitioningDelegate = heroManager
present(detailVC, animated: true)
}
}
// MARK: - KVTransitionAnimatable
extension HeroTransitionDemoViewController: KVTransitionAnimatable {
var imageViewFrame: CGRect? {
guard let imv = cellSelected?.getImageView() else { return .zero }
return imv.convert(imv.bounds, to: self.view)
}
var cornerRadius: CGFloat {
return 12
}
func heroImage() -> UIImage? {
return nil
}
func heroImageContentMode() -> UIView.ContentMode {
return .scaleAspectFill
}
func animationWillStart(transitionType: KVTransitionType) {
switch transitionType {
case .present:
cellSelected?.isHidden = true
case .dismiss:
break
}
}
func animationDidEnd(transitionType: KVTransitionType) {
switch transitionType {
case .present:
break
case .dismiss:
cellSelected?.isHidden = false
}
}
}
Step 3: Use convenience methods for different transition types
// Hero transition
presentWithHeroTransition(detailVC)
// Pinterest transition
presentWithPinterestTransition(detailVC)
That's it! ð
Component | Version |
---|---|
iOS | 13.0+ |
Swift | 5.3+ |
Xcode | 12.0+ |
Dependencies | None |
-
Hero Transition
- Smooth zoom transition between views
- Perfect for photo galleries and detail views
- Can be used to create banner-like transitions
-
Pinterest Transition
- Pinterest-style masonry layout transitions
- Great for grid-based UIs
- Supports interactive dismiss gestures
// Customize corner radius
var cornerRadius: CGFloat { return 12.0 }
// Choose content mode
func heroImageContentMode() -> UIView.ContentMode {
return .scaleAspectFill
}
// Handle animation lifecycle
func animationWillStart(transitionType: KVTransitionType) {
// Prepare for animation
}
func animationDidEnd(transitionType: KVTransitionType) {
// Handle completion
}
The core protocol that enables transitions:
protocol KVTransitionAnimatable {
var imageViewFrame: CGRect? { get }
var cornerRadius: CGFloat { get }
func heroImage() -> UIImage?
func heroImageContentMode() -> UIView.ContentMode
func animationWillStart(transitionType: KVTransitionType)
func animationDidEnd(transitionType: KVTransitionType)
}
-
heroImage()
- Returns the image to be used during the transition animation
- If returns
nil
, the system will automatically capture the screen content for transition - This is useful when you want to:
- Use a different image for transition than what's displayed
- Handle cases where the image might not be immediately available
- Fall back to screen capture when no specific image is provided
-
heroImageContentMode()
- Defines how the hero image should be displayed during transition
- Common values:
.scaleAspectFill
: Image fills the frame while maintaining aspect ratio (may crop).scaleAspectFit
: Image fits within the frame while maintaining aspect ratio.scaleToFill
: Image stretches to fill the frame (may distort)
- This affects how the image appears during the transition animation
Example implementation:
class PhotoDetailViewController: UIViewController, KVTransitionAnimatable {
private let imageView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFit
iv.translatesAutoresizingMaskIntoConstraints = false
return iv
}()
// MARK: - KVTransitionAnimatable
var imageViewFrame: CGRect? {
return imageView.frame
}
var cornerRadius: CGFloat {
return 12
}
func heroImage() -> UIImage? {
return imageView.image
}
func heroImageContentMode() -> UIView.ContentMode {
return .scaleAspectFill
}
func animationWillStart(transitionType: KVTransitionType) {
// Handle animation start
}
func animationDidEnd(transitionType: KVTransitionType) {
// Handle animation end
}
}
-
Transition Manager Retention
- Always store the transition manager as a strong reference in your view controller
- The
transitioningDelegate
is a weak reference, so the manager will be deallocated if not retained - Example:
private var transitionManager: Any? // or specific type // When presenting let manager = KVHeroTransitionManager(...) transitionManager = manager // Store strong reference detailVC.transitioningDelegate = manager
-
Memory Management
- The transition manager should be stored for the duration of the transition
- It can be released after the transition is complete
- Consider using a weak reference to the view controller in the manager to avoid retain cycles
-
Hero Image Handling
- Always provide a valid
imageViewFrame
for accurate transition positioning - Use
heroImage()
to control which image is used during transition - Choose appropriate
heroImageContentMode()
based on your UI requirements - Consider using screen capture (returning nil from
heroImage()
) when:- The image is not immediately available
- You want to transition the entire view content
- You need to handle complex view hierarchies
- Always provide a valid
- ð· Photo Gallery Apps - Hero transitions between thumbnail and full-size images
- ðïļ E-commerce Apps - Product grid to detail transitions
- ð° News Apps - Article preview to full article transitions
- ðĩ Music Apps - Album art transitions
- ðą Social Media - Profile picture and post transitions
- ðŠ Marketplace Apps - Item grid to detail view transitions
Clone and run the example:
git clone https://github.com/khanhVu-ops/KVHeroTransition.git
cd KVHeroTransition/Example
pod install
open KVHeroTransition.xcworkspace
The example demonstrates:
- Hero transitions
- Pinterest transitions
- Interactive dismiss gestures
- Custom corner radius animations
Q: Transition manager is nil after animation
- Store the transition manager as a strong reference in your view controller
- Use the convenience methods for transitions
- Make sure the manager is not deallocated during the transition
Q: Animation doesn't work with custom UIImageView
- Ensure
imageViewFrame
returns the correct frame - Check that the image view is properly configured
- Verify the view hierarchy is set up correctly
Q: Interactive dismiss not working
- Implement proper gesture handling
- Check view controller lifecycle methods
- Ensure the transition manager is properly retained
Khanh Vu
- ð§ Email: [email protected]
- ð GitHub: @khanhVu-ops
- ðž LinkedIn: Connect with me
KVHeroTransition is released under the MIT License. See LICENSE file for details.
iOS animation
hero transition
zoom animation
UIViewController transition
Swift animation library
iOS UI
Material Design
interactive transition
drag to dismiss
photo gallery transition
custom transition
iOS development
Swift package
CocoaPods
open source iOS
Made with âĪïļ by Khanh Vu
Building beautiful iOS transitions, one animation at a time