Mantis
An iOS Image cropping library, which mimics the Photo App written in Swift.
Install / Use
/learn @guoyingtao/MantisREADME
Mantis
Mantis is an iOS Image cropping library, which mimics the Photo App written in Swift and provides rich cropping interactions for your iOS/Mac app (Catalyst only).
<p align="center"> <img height="400" alt="Screenshot 2026-02-24 at 1 43 09 AM" src="https://github.com/user-attachments/assets/34932f3b-9174-4bf9-9807-38603f6edf05" /> <img src="Images/RotationDial.png" height="400" alt="Mantis RotaionDial" /> <img src="Images/SlideDial.png" height="400" alt="Mantis SlideDial" /> </p>🆕 What's New
Perspective Correction (Skew)
Mantis now supports Apple Photos–style perspective correction, allowing users to adjust horizontal and vertical skew in addition to straightening. When enabled, the slide dial displays three circular icon buttons — Straighten, Vertical, and Horizontal — letting users switch between adjustment modes with a single tap.
- Real-time 3D perspective preview powered by
CATransform3D - Accurate image export using
CIPerspectiveCorrection - Full integration with existing features: undo/redo, flip, 90° rotation, and preset transformations
Appearance Mode
Mantis now supports light, dark, and system appearance modes. By default Mantis uses a dark appearance (backward compatible). You can switch to a light theme or let it follow the system setting.
.forceDark— Always dark (default).forceLight— Always light, similar to Apple Photos in light mode.system— Follows the system light/dark mode setting
var config = Mantis.Config()
config.appearanceMode = .forceLight // or .system
let cropViewController = Mantis.cropViewController(image: <Your Image>, config: config)
Demos
<div align="center"> <video src="https://github.com/user-attachments/assets/732f0aab-21ab-4980-890f-4640432dec27" controls width="720"> </video> </div> <p align="center"> <img src="Images/Normal demos.gif" width="200" alt="Mantis Normal Demos" /> <img src="Images/Rotation dial demos.gif" width="200" alt="Mantis RotaionDial Demos" /> <img src="Images/Slide dial with flip demos.gif" width="200" alt="Mantis SlideDial Demos" /> </p>Mantis also provides rich crop shapes from the basic circle/square to polygon to arbitrary paths(We even provide a heart shape ❤️ 😏).
<p align="center"> <img src="Images/cropshapes.png" height="450" alt="Mantis" /> </p>Requirements
- iOS 12.0+
- MacOS 10.15+
- Xcode 10.0+
Install
<details> <summary><strong>CocoaPods</strong></summary>pod 'Mantis', '~> 2.31.1'
</details>
<details>
<summary><strong>Carthage</strong></summary>
github "guoyingtao/Mantis"
</details>
<details>
<summary><strong>Swift Packages</strong></summary>
- Repository: https://github.com/guoyingtao/Mantis.git
- Rules: Version - Exact - 2.31.1
Usage
<details> <summary><strong>Basic</strong></summary>- Create a CropViewController from Mantis with default config
You need set (cropViewController or its navigation controller).modalPresentationStyle = .fullscreen for iOS 13+ when the cropViewController is presented
UIKit
let cropViewController = Mantis.cropViewController(image: <Your Image>)
cropViewController.delegate = self
<Your ViewController>.present(cropViewController, animated: true)
SwiftUI
- Create an ImageCropperView from Mantis with default config
struct MyView: View {
@State private var image: UIImage?
@State private var transformation: Transformation?
@State private var cropInfo: CropInfo?
var body: some View {
ImageCropperView(
image: $image,
transformation: $transformation,
cropInfo: $cropInfo
)
}
}
Note:
- To start a crop operation programmatically, use the existing
actionbinding(forImageCropperView):action = .crop- To receive the result of the crop (success or failure), use the new
onCropCompletedcallback.
This is especially useful because cropping may not complete instantly in all cases, so relying on this callback ensures you update your UI only after the operation finishes.
- The caller needs to conform CropViewControllerDelegate
public protocol CropViewControllerDelegate: class {
func cropViewControllerDidCrop(_ cropViewController: CropViewController, cropped: UIImage, transformation: Transformation, cropInfo: CropInfo)
func cropViewControllerDidCancel(_ cropViewController: CropViewController, original: UIImage)
// The implementation of the following functions are optional
func cropViewControllerDidFailToCrop(_ cropViewController: CropViewController, original: UIImage)
func cropViewControllerDidBeginResize(_ cropViewController: CropViewController)
func cropViewControllerDidEndResize(_ cropViewController: CropViewController, original: UIImage, cropInfo: CropInfo)
}
</details>
<details>
<summary><strong>CropToolbar mode</strong></summary>
-
CropToolbar has two modes:
- normal mode
In normal mode, you can use a set of standard CropViewController photo editing features with "Cancel" and "Done" buttons.
let cropViewController = Mantis.cropViewController(image: <Your Image>)
- embedded mode
This mode does not include "Cancel" and "Done" buttons, so you can embed CropViewController into another view controller
<p align="center"> <img src="Images/customizable.jpg" height="300" alt="Mantis" /> </p>var config = Mantis.Config()
config.cropToolbarConfig.mode = .embedded
let cropViewController = Mantis.cropViewController(image: <Your Image>, config: config)
</details>
<details>
<summary><strong>Add your own ratio</strong></summary>
// Add a custom ratio 1:2 for portrait orientation
let config = Mantis.Config()
config.addCustomRatio(byVerticalWidth: 1, andVerticalHeight: 2)
<Your Crop ViewController> = Mantis.cropViewController(image: <Your Image>, config: config)
// Set the ratioOptions of the config if you don't want to keep all default ratios
let config = Mantis.Config()
//config.ratioOptions = [.original, .square, .custom]
config.ratioOptions = [.custom]
config.addCustomRatio(byVerticalWidth: 1, andVerticalHeight: 2)
<Your Crop ViewController> = Mantis.cropViewController(image: <Your Image>, config: config)
- If you always want to use only one fixed ratio, set Mantis.Config.presetFixedRatioType = alwaysUsingOnePresetFixedRatio
<Your Crop ViewController>.config.presetFixedRatioType = .alwaysUsingOnePresetFixedRatio(ratio: 16.0 / 9.0)
When choose alwaysUsingOnePresetFixedRatio, fixed-ratio setting button does not show.
- If you want to hide rotation control view, set Mantis.Config.cropViewConfig.showAttachedRotationControlView = false
- If you want to use ratio list instead of presenter, set Mantis.CropToolbarConfig.ratioCandidatesShowType = .alwaysShowRatioList
public enum RatioCandidatesShowType {
case presentRatioList
case alwaysShowRatioList
}
- If you build your custom toolbar you can add your own fixed ratio buttons
// set a custom fixed ratio
cropToolbarDelegate?.didSelectRatio(ratio: 9 / 16)
</details>
<details>
<summary><strong>Crop shapes</strong></summary>
- If you want to set different crop shape, set Mantis.Config.cropViewConfig.cropShapeType
public enum CropShapeType {
case rect
case square
case ellipse
case circle(maskOnly: Bool = false)
case diamond(maskOnly: Bool = false)
case heart(maskOnly: Bool = false)
case polygon(sides: Int, offset: CGFloat = 0, maskOnly: Bool = false)
case path(points: [CGPoint], maskOnly: Bool = false)
}
</details>
<details>
<summary><strong>Preset transformations</strong></summary>
- If you want to apply transformations when showing an image, set Mantis.Config.cropViewConfig.presetTransformationType
public enum PresetTransformationType {
case none
case presetInfo(info: Transformation)
case presetNormalizedInfo(normalizedInfo: CGRect)
}
Please use the transformation information obtained previously from delegate method cropViewControllerDidCrop(_ cropViewController: CropViewController, cropped: UIImage, transformation: Transformation, , cropInfo: CropInfo).
</details> <details> <summary><strong>Undo/Redo Support</strong></summary>-
Mantis offers full support for Undo, Redo, Revert to Original in both iOS and Catalyst.
-
If you want to add support for this feature, set Mantis.Config.enableUndoRedo = true
-
Catalyst menus for this feature are localized.
- Enable perspective correction to let users adjust horizontal and vertical skew, similar to the Apple Photos app.
var config = Mantis.Config()
config.cropViewConfig.enablePerspectiveCorrection = true
let cropViewController = Mantis.cropViewController(image: <Your Image>, config: config)
When enablePerspectiveCorrection is true, the slide dial is used by default (no need to set builtInRotationControlViewType explicitly) and automatically switches to withTypeSelector mode, showing three circular icon buttons (Straighten / Vertical / Horizontal) above the ruler.
