Image Crop Tool using SwiftUI
Image Cropping Application Tutorial
This tutorial explains how to implement an image cropping feature in SwiftUI using a Model-View-ViewModel (MVVM) architecture. The application allows users to select images, crop them with a draggable overlay, and save the results.
Future enhancements could include aspect ratio controls, rotation, or more advanced image processing features.
Architecture Overview
The application consists of three main components:
ImagePickerView
- Main view handling UI and user interactionsImagePickerViewModel
- Business logic and state managementCropOverlayView
- Custom view for the cropping interface
1. Image Selection and Display
The ImagePickerView
uses SwiftUI’s PhotosPicker
for image selection:
PhotosPicker(selection: $selectedItem, matching: .images, photoLibrary: .shared()) {
Label("Select", systemImage: "photo")
}
.buttonStyle(.borderedProminent)
When an image is selected, it’s processed by the ViewModel:
func loadImage(from item: PhotosPickerItem?) async {
guard let item = item else { return }
croppedImage = nil
cachedDisplaySize = nil // Reset cache when loading new image
do {
if let data = try await item.loadTransferable(type: Data.self),
let uiImage = UIImage(data: data) {
selectedImage = uiImage
}
} catch {
print("Failed to load image: \(error.localizedDescription)")
}
}
2. Crop Overlay Implementation
The CropOverlayView
creates a draggable crop rectangle with a semi-transparent overlay:
struct CropOverlayView: View {
@Binding var cropRect: CGRect
var body: some View {
GeometryReader { geometry in
ZStack {
// Semi-transparent overlay
Color.black.opacity(0.5)
.mask(
Rectangle()
.overlay(
Rectangle()
.frame(width: cropRect.width, height: cropRect.height)
.position(x: cropRect.midX, y: cropRect.midY)
.blendMode(.destinationOut)
)
)
// Crop rectangle border
Rectangle()
.strokeBorder(Color.white, lineWidth: 2)
.frame(width: cropRect.width, height: cropRect.height)
.position(x: cropRect.midX, y: cropRect.midY)
}
}
}
}
3. Crop Mode Management
The application uses a isCropModeActive
state to manage the cropping interface:
func toggleCropMode() {
isCropModeActive.toggle()
if !isCropModeActive {
// Reset crop rect when exiting crop mode
cropRect = .zero
}
}
4. Image Cropping Logic
The crucial cropping implementation in the ViewModel:
func cropImage() {
guard let image = selectedImage else { return }
let displaySize = getDisplaySize(for: image)
// Calculate scaling factor from display size to actual image size
let scaleX = image.size.width / displaySize.width
let scaleY = image.size.height / displaySize.height
// Convert crop rect to image coordinates
let imageCropRect = CGRect(
x: cropRect.origin.x * scaleX,
y: cropRect.origin.y * scaleY,
width: cropRect.width * scaleX,
height: cropRect.height * scaleY
)
// Ensure crop rect is within image bounds
let imageBounds = CGRect(origin: .zero, size: image.size)
let validCropRect = imageCropRect.intersection(imageBounds)
if let cgImage = image.cgImage,
let croppedCGImage = cgImage.cropping(to: validCropRect) {
croppedImage = UIImage(cgImage: croppedCGImage)
}
}
5. Display Size Calculations
The application maintains proper scaling between display and actual image sizes:
private func getDisplaySize(
for image: UIImage,
containerWidth: CGFloat = UIScren.main.bounds.width
) -> CGSize {
if let cached = cachedDisplaySize {
return cached
}
let imageAspect = image.size.width / image.size.height
let displayHeight = min(400, containerWidth / imageAspect)
let displayWidth = displayHeight * imageAspect
let size = CGSize(width: displayWidth, height: displayHeight)
cachedDisplaySize = size
return size
}
Key Features
- Image Selection: Uses PhotosPicker for native iOS photo library access
- Interactive Cropping: Draggable crop rectangle with visual feedback
- Proper Scaling: Maintains correct proportions between display and actual image sizes
- State Management: Clear separation of concerns with MVVM architecture
- Crop Mode: Dedicated mode for cropping operations with appropriate UI states
Implementation Considerations
- Performance: Display size calculations are cached for efficiency
- Error Handling: Basic error handling for image loading failures
- UI/UX: Clear mode transitions and intuitive controls
- Memory Management: Proper cleanup of resources when loading new images
- Constraints: Crop rectangle is constrained within image bounds
This implementation provides a foundation for image cropping functionality that can be extended with additional features like aspect ratio controls, rotation, or more sophisticated image processing capabilities.