├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug-report.md │ ├── feature-request.md │ └── question.md └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── CropViewController.podspec ├── Gemfile ├── Gemfile.lock ├── Images ├── screenshot.webp ├── screenshot2015.jpg ├── screenshot2016.jpg ├── screenshot2017.jpg ├── screenshot2018.jpg ├── screenshot2020.png ├── users-2020.png └── users.png ├── LICENSE ├── Objective-C ├── TOCropViewController │ ├── Categories │ │ ├── UIImage+CropRotate.h │ │ └── UIImage+CropRotate.m │ ├── Constants │ │ └── TOCropViewConstants.h │ ├── Models │ │ ├── TOActivityCroppedImageProvider.h │ │ ├── TOActivityCroppedImageProvider.m │ │ ├── TOCropViewControllerTransitioning.h │ │ ├── TOCropViewControllerTransitioning.m │ │ ├── TOCroppedImageAttributes.h │ │ └── TOCroppedImageAttributes.m │ ├── Resources │ │ ├── Base.lproj │ │ │ └── TOCropViewControllerLocalizable.strings │ │ ├── PrivacyInfo.xcprivacy │ │ ├── ar.lproj │ │ │ └── TOCropViewControllerLocalizable.strings │ │ ├── ca.lproj │ │ │ └── TOCropViewControllerLocalizable.strings │ │ ├── cs.lproj │ │ │ └── TOCropViewControllerLocalizable.strings │ │ ├── da-DK.lproj │ │ │ └── TOCropViewControllerLocalizable.strings │ │ ├── de.lproj │ │ │ └── TOCropViewControllerLocalizable.strings │ │ ├── en.lproj │ │ │ └── TOCropViewControllerLocalizable.strings │ │ ├── es.lproj │ │ │ └── TOCropViewControllerLocalizable.strings │ │ ├── fa-IR.lproj │ │ │ └── TOCropViewControllerLocalizable.strings │ │ ├── fa.lproj │ │ │ └── TOCropViewControllerLocalizable.strings │ │ ├── fi.lproj │ │ │ └── TOCropViewControllerLocalizable.strings │ │ ├── fr.lproj │ │ │ └── TOCropViewControllerLocalizable.strings │ │ ├── hu.lproj │ │ │ └── TOCropViewControllerLocalizable.strings │ │ ├── id.lproj │ │ │ └── TOCropViewControllerLocalizable.strings │ │ ├── it.lproj │ │ │ └── TOCropViewControllerLocalizable.strings │ │ ├── ja.lproj │ │ │ └── TOCropViewControllerLocalizable.strings │ │ ├── ko.lproj │ │ │ └── TOCropViewControllerLocalizable.strings │ │ ├── ms.lproj │ │ │ └── TOCropViewControllerLocalizable.strings │ │ ├── nl.lproj │ │ │ └── TOCropViewControllerLocalizable.strings │ │ ├── pl.lproj │ │ │ └── TOCropViewControllerLocalizable.strings │ │ ├── pt-BR.lproj │ │ │ └── TOCropViewControllerLocalizable.strings │ │ ├── pt.lproj │ │ │ └── TOCropViewControllerLocalizable.strings │ │ ├── ro.lproj │ │ │ └── TOCropViewControllerLocalizable.strings │ │ ├── ru.lproj │ │ │ └── TOCropViewControllerLocalizable.strings │ │ ├── sk.lproj │ │ │ └── TOCropViewControllerLocalizable.strings │ │ ├── tr.lproj │ │ │ └── TOCropViewControllerLocalizable.strings │ │ ├── uk.lproj │ │ │ └── TOCropViewControllerLocalizable.strings │ │ ├── vi.lproj │ │ │ └── TOCropViewControllerLocalizable.strings │ │ ├── zh-Hans.lproj │ │ │ └── TOCropViewControllerLocalizable.strings │ │ └── zh-Hant.lproj │ │ │ └── TOCropViewControllerLocalizable.strings │ ├── Supporting │ │ └── Info.plist │ ├── TOCropViewController.h │ ├── TOCropViewController.m │ ├── Views │ │ ├── TOCropOverlayView.h │ │ ├── TOCropOverlayView.m │ │ ├── TOCropScrollView.h │ │ ├── TOCropScrollView.m │ │ ├── TOCropToolbar.h │ │ ├── TOCropToolbar.m │ │ ├── TOCropView.h │ │ └── TOCropView.m │ └── include │ │ ├── TOActivityCroppedImageProvider.h │ │ ├── TOCropOverlayView.h │ │ ├── TOCropScrollView.h │ │ ├── TOCropToolbar.h │ │ ├── TOCropView.h │ │ ├── TOCropViewConstants.h │ │ ├── TOCropViewController.h │ │ ├── TOCropViewControllerTransitioning.h │ │ ├── TOCroppedImageAttributes.h │ │ ├── UIImage+CropRotate.h │ │ └── module.modulemap ├── TOCropViewControllerExample-Extension │ ├── Info.plist │ ├── ShareViewController.h │ └── ShareViewController.m ├── TOCropViewControllerExample │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Base.lproj │ │ ├── LaunchScreen.xib │ │ └── Main.storyboard │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ ├── TOCropViewControllerExample-Extension.entitlements │ ├── TOCropViewControllerExample.entitlements │ ├── ViewController.h │ ├── ViewController.m │ ├── ar.lproj │ │ ├── LaunchScreen.strings │ │ └── Main.strings │ ├── fr.lproj │ │ ├── LaunchScreen.strings │ │ └── Main.strings │ ├── main.m │ └── tr.lproj │ │ └── Main.strings └── TOCropViewControllerTests │ ├── Info.plist │ └── TOCropViewControllerTests.m ├── Package.swift ├── README.md ├── Swift ├── CropViewController │ ├── CropViewController.h │ ├── CropViewController.swift │ └── Info.plist └── CropViewControllerExample │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── CropViewController-Bridging-Header.h │ ├── Info.plist │ └── ViewController.swift ├── TOCropViewController.podspec ├── TOCropViewControllerExample.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── xcshareddata │ └── xcschemes │ ├── CropViewController.xcscheme │ ├── CropViewControllerExample.xcscheme │ ├── TOCropViewController.xcscheme │ ├── TOCropViewControllerExample-Extension.xcscheme │ ├── TOCropViewControllerExample.xcscheme │ └── TOCropViewControllerTests.xcscheme ├── breakdown.jpg ├── buildkite ├── pipeline.release.yml └── pipeline.test.yml └── fastlane └── Fastfile /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: timoliver 2 | custom: https://tim.dev/paypal 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Report a bug you may have found in TOCropViewController 4 | title: '' 5 | labels: bug 6 | assignees: TimOliver 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **iOS Device:** 27 | - Device: [e.g. iPhone6] 28 | - OS: [e.g. iOS8.1] 29 | - Library Version [e.g. 22] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest a new idea for TOCropViewController 4 | title: '' 5 | labels: feature 6 | assignees: TimOliver 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask a general question about how to use TOCropViewController 4 | title: '' 5 | labels: question 6 | assignees: TimOliver 7 | 8 | --- 9 | 10 | **What are you trying to achieve with this library exactly? Please describe.** 11 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: macos-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Run a one-line script 13 | run: 'bundle install && bundle exec fastlane test' 14 | env: 15 | TEST_SCHEME: "TOCropViewControllerTests" 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | 92 | ## Misc 93 | .DS_Store 94 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at info@timoliver.com.au. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to `TOCropViewController` 2 | 3 | Thanks so much for your interest in `TOCropViewController`! It makes me incredibly happy to hear that others have not just found it useful, but are eager to help contribute to it. 4 | 5 | ## Submitting a Pull Request 6 | At this point, this library is pretty much a delicate house of cards. When modifying any of the code involved with the UI or layout, a lot of manual testing needs to be done to ensure that no regressions were introduced. 7 | 8 | If you've added or changed a feature that directly involves any UI layout, please test the following to ensure nothing has broken. 9 | * Presenting and dismissing the view controller in both portrait and landscape modes. 10 | * Presenting the view controller, rotating the device and then dismissing from the new orientation. 11 | * Presenting the view controller, then enabling split-screen on an iPad. 12 | * Changing the split-screen window sizes on iPad. 13 | 14 | If possible, please file an issue before filing a PR to discuss the feature you'd like to add. To ensure the quality of this view controller library doesn't dip, I plan to be very strict about the level of reliability and thoroughness of any code submitted through a PR. :) 15 | 16 | ## Submitting an Issue 17 | I've included all of the essential tips for filing comprehensive issues directly in the [issues template](/TimOliver/TOCropViewController/blob/master/ISSUE_TEMPLATE.md). Please read that document and follow it as closely as you can when filing new issues. 18 | 19 | --- 20 | 21 | Thanks again for your interest in `TOCropViewController`! I hope you've found the library useful in your apps! 22 | -------------------------------------------------------------------------------- /CropViewController.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'CropViewController' 3 | s.version = '2.7.4' 4 | s.license = { :type => 'MIT', :file => 'LICENSE' } 5 | s.summary = 'A Swift view controller that enables cropping and rotating of UIImage objects.' 6 | s.homepage = 'https://github.com/TimOliver/TOCropViewController' 7 | s.author = 'Tim Oliver' 8 | s.source = { :git => 'https://github.com/TimOliver/TOCropViewController.git', :tag => s.version } 9 | s.platform = :ios, '11.0' 10 | s.source_files = 'Swift/CropViewController/**/*.{h,swift}', 'Objective-C/TOCropViewController/**/*.{h,m}' 11 | s.exclude_files = 'Objective-C/TOCropViewController/include/**/*.h' 12 | s.resource_bundles = { 13 | 'TOCropViewControllerBundle' => ['Objective-C/TOCropViewController/**/*.{lproj,xcprivacy}'] 14 | } 15 | s.requires_arc = true 16 | s.swift_version = '5.0' 17 | end 18 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "fastlane" 4 | gem 'cocoapods' -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.7) 5 | base64 6 | nkf 7 | rexml 8 | activesupport (7.1.3.2) 9 | base64 10 | bigdecimal 11 | concurrent-ruby (~> 1.0, >= 1.0.2) 12 | connection_pool (>= 2.2.5) 13 | drb 14 | i18n (>= 1.6, < 2) 15 | minitest (>= 5.1) 16 | mutex_m 17 | tzinfo (~> 2.0) 18 | addressable (2.8.6) 19 | public_suffix (>= 2.0.2, < 6.0) 20 | algoliasearch (1.27.5) 21 | httpclient (~> 2.8, >= 2.8.3) 22 | json (>= 1.5.1) 23 | artifactory (3.0.17) 24 | atomos (0.1.3) 25 | aws-eventstream (1.3.0) 26 | aws-partitions (1.909.0) 27 | aws-sdk-core (3.191.6) 28 | aws-eventstream (~> 1, >= 1.3.0) 29 | aws-partitions (~> 1, >= 1.651.0) 30 | aws-sigv4 (~> 1.8) 31 | jmespath (~> 1, >= 1.6.1) 32 | aws-sdk-kms (1.78.0) 33 | aws-sdk-core (~> 3, >= 3.191.0) 34 | aws-sigv4 (~> 1.1) 35 | aws-sdk-s3 (1.146.1) 36 | aws-sdk-core (~> 3, >= 3.191.0) 37 | aws-sdk-kms (~> 1) 38 | aws-sigv4 (~> 1.8) 39 | aws-sigv4 (1.8.0) 40 | aws-eventstream (~> 1, >= 1.0.2) 41 | babosa (1.0.4) 42 | base64 (0.2.0) 43 | bigdecimal (3.1.7) 44 | claide (1.1.0) 45 | cocoapods (1.15.2) 46 | addressable (~> 2.8) 47 | claide (>= 1.0.2, < 2.0) 48 | cocoapods-core (= 1.15.2) 49 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 50 | cocoapods-downloader (>= 2.1, < 3.0) 51 | cocoapods-plugins (>= 1.0.0, < 2.0) 52 | cocoapods-search (>= 1.0.0, < 2.0) 53 | cocoapods-trunk (>= 1.6.0, < 2.0) 54 | cocoapods-try (>= 1.1.0, < 2.0) 55 | colored2 (~> 3.1) 56 | escape (~> 0.0.4) 57 | fourflusher (>= 2.3.0, < 3.0) 58 | gh_inspector (~> 1.0) 59 | molinillo (~> 0.8.0) 60 | nap (~> 1.0) 61 | ruby-macho (>= 2.3.0, < 3.0) 62 | xcodeproj (>= 1.23.0, < 2.0) 63 | cocoapods-core (1.15.2) 64 | activesupport (>= 5.0, < 8) 65 | addressable (~> 2.8) 66 | algoliasearch (~> 1.0) 67 | concurrent-ruby (~> 1.1) 68 | fuzzy_match (~> 2.0.4) 69 | nap (~> 1.0) 70 | netrc (~> 0.11) 71 | public_suffix (~> 4.0) 72 | typhoeus (~> 1.0) 73 | cocoapods-deintegrate (1.0.5) 74 | cocoapods-downloader (2.1) 75 | cocoapods-plugins (1.0.0) 76 | nap 77 | cocoapods-search (1.0.1) 78 | cocoapods-trunk (1.6.0) 79 | nap (>= 0.8, < 2.0) 80 | netrc (~> 0.11) 81 | cocoapods-try (1.2.0) 82 | colored (1.2) 83 | colored2 (3.1.2) 84 | commander (4.6.0) 85 | highline (~> 2.0.0) 86 | concurrent-ruby (1.2.3) 87 | connection_pool (2.4.1) 88 | declarative (0.0.20) 89 | digest-crc (0.6.5) 90 | rake (>= 12.0.0, < 14.0.0) 91 | domain_name (0.6.20240107) 92 | dotenv (2.8.1) 93 | drb (2.2.1) 94 | emoji_regex (3.2.3) 95 | escape (0.0.4) 96 | ethon (0.16.0) 97 | ffi (>= 1.15.0) 98 | excon (0.110.0) 99 | faraday (1.10.3) 100 | faraday-em_http (~> 1.0) 101 | faraday-em_synchrony (~> 1.0) 102 | faraday-excon (~> 1.1) 103 | faraday-httpclient (~> 1.0) 104 | faraday-multipart (~> 1.0) 105 | faraday-net_http (~> 1.0) 106 | faraday-net_http_persistent (~> 1.0) 107 | faraday-patron (~> 1.0) 108 | faraday-rack (~> 1.0) 109 | faraday-retry (~> 1.0) 110 | ruby2_keywords (>= 0.0.4) 111 | faraday-cookie_jar (0.0.7) 112 | faraday (>= 0.8.0) 113 | http-cookie (~> 1.0.0) 114 | faraday-em_http (1.0.0) 115 | faraday-em_synchrony (1.0.0) 116 | faraday-excon (1.1.0) 117 | faraday-httpclient (1.0.1) 118 | faraday-multipart (1.0.4) 119 | multipart-post (~> 2) 120 | faraday-net_http (1.0.1) 121 | faraday-net_http_persistent (1.2.0) 122 | faraday-patron (1.0.0) 123 | faraday-rack (1.0.0) 124 | faraday-retry (1.0.3) 125 | faraday_middleware (1.2.0) 126 | faraday (~> 1.0) 127 | fastimage (2.3.1) 128 | fastlane (2.220.0) 129 | CFPropertyList (>= 2.3, < 4.0.0) 130 | addressable (>= 2.8, < 3.0.0) 131 | artifactory (~> 3.0) 132 | aws-sdk-s3 (~> 1.0) 133 | babosa (>= 1.0.3, < 2.0.0) 134 | bundler (>= 1.12.0, < 3.0.0) 135 | colored (~> 1.2) 136 | commander (~> 4.6) 137 | dotenv (>= 2.1.1, < 3.0.0) 138 | emoji_regex (>= 0.1, < 4.0) 139 | excon (>= 0.71.0, < 1.0.0) 140 | faraday (~> 1.0) 141 | faraday-cookie_jar (~> 0.0.6) 142 | faraday_middleware (~> 1.0) 143 | fastimage (>= 2.1.0, < 3.0.0) 144 | gh_inspector (>= 1.1.2, < 2.0.0) 145 | google-apis-androidpublisher_v3 (~> 0.3) 146 | google-apis-playcustomapp_v1 (~> 0.1) 147 | google-cloud-env (>= 1.6.0, < 2.0.0) 148 | google-cloud-storage (~> 1.31) 149 | highline (~> 2.0) 150 | http-cookie (~> 1.0.5) 151 | json (< 3.0.0) 152 | jwt (>= 2.1.0, < 3) 153 | mini_magick (>= 4.9.4, < 5.0.0) 154 | multipart-post (>= 2.0.0, < 3.0.0) 155 | naturally (~> 2.2) 156 | optparse (>= 0.1.1, < 1.0.0) 157 | plist (>= 3.1.0, < 4.0.0) 158 | rubyzip (>= 2.0.0, < 3.0.0) 159 | security (= 0.1.5) 160 | simctl (~> 1.6.3) 161 | terminal-notifier (>= 2.0.0, < 3.0.0) 162 | terminal-table (~> 3) 163 | tty-screen (>= 0.6.3, < 1.0.0) 164 | tty-spinner (>= 0.8.0, < 1.0.0) 165 | word_wrap (~> 1.0.0) 166 | xcodeproj (>= 1.13.0, < 2.0.0) 167 | xcpretty (~> 0.3.0) 168 | xcpretty-travis-formatter (>= 0.0.3, < 2.0.0) 169 | ffi (1.16.3) 170 | fourflusher (2.3.1) 171 | fuzzy_match (2.0.4) 172 | gh_inspector (1.1.3) 173 | google-apis-androidpublisher_v3 (0.54.0) 174 | google-apis-core (>= 0.11.0, < 2.a) 175 | google-apis-core (0.11.3) 176 | addressable (~> 2.5, >= 2.5.1) 177 | googleauth (>= 0.16.2, < 2.a) 178 | httpclient (>= 2.8.1, < 3.a) 179 | mini_mime (~> 1.0) 180 | representable (~> 3.0) 181 | retriable (>= 2.0, < 4.a) 182 | rexml 183 | google-apis-iamcredentials_v1 (0.17.0) 184 | google-apis-core (>= 0.11.0, < 2.a) 185 | google-apis-playcustomapp_v1 (0.13.0) 186 | google-apis-core (>= 0.11.0, < 2.a) 187 | google-apis-storage_v1 (0.31.0) 188 | google-apis-core (>= 0.11.0, < 2.a) 189 | google-cloud-core (1.7.0) 190 | google-cloud-env (>= 1.0, < 3.a) 191 | google-cloud-errors (~> 1.0) 192 | google-cloud-env (1.6.0) 193 | faraday (>= 0.17.3, < 3.0) 194 | google-cloud-errors (1.4.0) 195 | google-cloud-storage (1.47.0) 196 | addressable (~> 2.8) 197 | digest-crc (~> 0.4) 198 | google-apis-iamcredentials_v1 (~> 0.1) 199 | google-apis-storage_v1 (~> 0.31.0) 200 | google-cloud-core (~> 1.6) 201 | googleauth (>= 0.16.2, < 2.a) 202 | mini_mime (~> 1.0) 203 | googleauth (1.8.1) 204 | faraday (>= 0.17.3, < 3.a) 205 | jwt (>= 1.4, < 3.0) 206 | multi_json (~> 1.11) 207 | os (>= 0.9, < 2.0) 208 | signet (>= 0.16, < 2.a) 209 | highline (2.0.3) 210 | http-cookie (1.0.5) 211 | domain_name (~> 0.5) 212 | httpclient (2.8.3) 213 | i18n (1.14.4) 214 | concurrent-ruby (~> 1.0) 215 | jmespath (1.6.2) 216 | json (2.7.2) 217 | jwt (2.8.1) 218 | base64 219 | mini_magick (4.12.0) 220 | mini_mime (1.1.5) 221 | minitest (5.22.3) 222 | molinillo (0.8.0) 223 | multi_json (1.15.0) 224 | multipart-post (2.4.0) 225 | mutex_m (0.2.0) 226 | nanaimo (0.3.0) 227 | nap (1.1.0) 228 | naturally (2.2.1) 229 | netrc (0.11.0) 230 | nkf (0.2.0) 231 | optparse (0.4.0) 232 | os (1.1.4) 233 | plist (3.7.1) 234 | public_suffix (4.0.7) 235 | rake (13.2.1) 236 | representable (3.2.0) 237 | declarative (< 0.1.0) 238 | trailblazer-option (>= 0.1.1, < 0.2.0) 239 | uber (< 0.2.0) 240 | retriable (3.1.2) 241 | rexml (3.2.6) 242 | rouge (2.0.7) 243 | ruby-macho (2.5.1) 244 | ruby2_keywords (0.0.5) 245 | rubyzip (2.3.2) 246 | security (0.1.5) 247 | signet (0.19.0) 248 | addressable (~> 2.8) 249 | faraday (>= 0.17.5, < 3.a) 250 | jwt (>= 1.5, < 3.0) 251 | multi_json (~> 1.10) 252 | simctl (1.6.10) 253 | CFPropertyList 254 | naturally 255 | terminal-notifier (2.0.0) 256 | terminal-table (3.0.2) 257 | unicode-display_width (>= 1.1.1, < 3) 258 | trailblazer-option (0.1.2) 259 | tty-cursor (0.7.1) 260 | tty-screen (0.8.2) 261 | tty-spinner (0.9.3) 262 | tty-cursor (~> 0.7) 263 | typhoeus (1.4.1) 264 | ethon (>= 0.9.0) 265 | tzinfo (2.0.6) 266 | concurrent-ruby (~> 1.0) 267 | uber (0.1.0) 268 | unicode-display_width (2.5.0) 269 | word_wrap (1.0.0) 270 | xcodeproj (1.24.0) 271 | CFPropertyList (>= 2.3.3, < 4.0) 272 | atomos (~> 0.1.3) 273 | claide (>= 1.0.2, < 2.0) 274 | colored2 (~> 3.1) 275 | nanaimo (~> 0.3.0) 276 | rexml (~> 3.2.4) 277 | xcpretty (0.3.0) 278 | rouge (~> 2.0.7) 279 | xcpretty-travis-formatter (1.0.1) 280 | xcpretty (~> 0.2, >= 0.0.7) 281 | 282 | PLATFORMS 283 | ruby 284 | 285 | DEPENDENCIES 286 | cocoapods 287 | fastlane 288 | 289 | BUNDLED WITH 290 | 2.5.7 291 | -------------------------------------------------------------------------------- /Images/screenshot.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TimOliver/TOCropViewController/a634cb7cdfd580006e79a6e74e64417fe9e9783b/Images/screenshot.webp -------------------------------------------------------------------------------- /Images/screenshot2015.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TimOliver/TOCropViewController/a634cb7cdfd580006e79a6e74e64417fe9e9783b/Images/screenshot2015.jpg -------------------------------------------------------------------------------- /Images/screenshot2016.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TimOliver/TOCropViewController/a634cb7cdfd580006e79a6e74e64417fe9e9783b/Images/screenshot2016.jpg -------------------------------------------------------------------------------- /Images/screenshot2017.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TimOliver/TOCropViewController/a634cb7cdfd580006e79a6e74e64417fe9e9783b/Images/screenshot2017.jpg -------------------------------------------------------------------------------- /Images/screenshot2018.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TimOliver/TOCropViewController/a634cb7cdfd580006e79a6e74e64417fe9e9783b/Images/screenshot2018.jpg -------------------------------------------------------------------------------- /Images/screenshot2020.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TimOliver/TOCropViewController/a634cb7cdfd580006e79a6e74e64417fe9e9783b/Images/screenshot2020.png -------------------------------------------------------------------------------- /Images/users-2020.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TimOliver/TOCropViewController/a634cb7cdfd580006e79a6e74e64417fe9e9783b/Images/users-2020.png -------------------------------------------------------------------------------- /Images/users.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TimOliver/TOCropViewController/a634cb7cdfd580006e79a6e74e64417fe9e9783b/Images/users.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2024 Tim Oliver 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Categories/UIImage+CropRotate.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+CropRotate.h 3 | // 4 | // Copyright 2015-2024 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import 24 | 25 | NS_ASSUME_NONNULL_BEGIN 26 | 27 | @interface UIImage (TOCropRotate) 28 | 29 | /// Crops a portion of an existing image object and returns it as a new image 30 | /// @param frame The region inside the image (In image pixel space) to crop 31 | /// @param angle If any, the angle the image is rotated at as well 32 | /// @param circular Whether the resulting image is returned as a square or a circle 33 | - (nonnull UIImage *)croppedImageWithFrame:(CGRect)frame 34 | angle:(NSInteger)angle 35 | circularClip:(BOOL)circular; 36 | 37 | @end 38 | 39 | NS_ASSUME_NONNULL_END 40 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Categories/UIImage+CropRotate.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+CropRotate.m 3 | // 4 | // Copyright 2015-2024 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import "UIImage+CropRotate.h" 24 | 25 | @implementation UIImage (CropRotate) 26 | 27 | - (BOOL)hasAlpha 28 | { 29 | CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(self.CGImage); 30 | return (alphaInfo == kCGImageAlphaFirst || alphaInfo == kCGImageAlphaLast || 31 | alphaInfo == kCGImageAlphaPremultipliedFirst || alphaInfo == kCGImageAlphaPremultipliedLast); 32 | } 33 | 34 | - (UIImage *)croppedImageWithFrame:(CGRect)frame angle:(NSInteger)angle circularClip:(BOOL)circular 35 | { 36 | UIGraphicsImageRendererFormat *format = [UIGraphicsImageRendererFormat new]; 37 | format.opaque = !self.hasAlpha && !circular; 38 | format.scale = self.scale; 39 | 40 | UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:frame.size format:format]; 41 | UIImage *croppedImage = [renderer imageWithActions:^(UIGraphicsImageRendererContext *rendererContext) { 42 | CGContextRef context = rendererContext.CGContext; 43 | 44 | // If we're capturing a circular image, set the clip mask first 45 | if (circular) { 46 | CGContextAddEllipseInRect(context, (CGRect){CGPointZero, frame.size}); 47 | CGContextClip(context); 48 | } 49 | 50 | // Offset the origin (Which is the top left corner) to start where our cropping origin is 51 | CGContextTranslateCTM(context, -frame.origin.x, -frame.origin.y); 52 | 53 | // If an angle was supplied, rotate the entire canvas + coordinate space to match 54 | if (angle != 0) { 55 | // Rotation in radians 56 | CGFloat rotation = angle * (M_PI/180.0f); 57 | 58 | // Work out the new bounding size of the canvas after rotation 59 | CGRect imageBounds = (CGRect){CGPointZero, self.size}; 60 | CGRect rotatedBounds = CGRectApplyAffineTransform(imageBounds, 61 | CGAffineTransformMakeRotation(rotation)); 62 | // As we're rotating from the top left corner, and not the center of the canvas, the frame 63 | // will have rotated out of our visible canvas. Compensate for this. 64 | CGContextTranslateCTM(context, -rotatedBounds.origin.x, -rotatedBounds.origin.y); 65 | 66 | // Perform the rotation transformation 67 | CGContextRotateCTM(context, rotation); 68 | } 69 | 70 | // Draw the image with all of the transformation parameters applied. 71 | // We do not need to worry about specifying the size here since we're already 72 | // constrained by the context image size 73 | [self drawAtPoint:CGPointZero]; 74 | }]; 75 | 76 | // Re-apply the retina scale we originally had 77 | return [UIImage imageWithCGImage:croppedImage.CGImage scale:self.scale orientation:UIImageOrientationUp]; 78 | } 79 | 80 | @end 81 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Constants/TOCropViewConstants.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOCropViewConstants.h 3 | // 4 | // Copyright 2015-2024 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import 24 | 25 | /** 26 | The shape of the cropping region of this crop view controller 27 | */ 28 | typedef NS_ENUM(NSInteger, TOCropViewCroppingStyle) { 29 | TOCropViewCroppingStyleDefault, // The regular, rectangular crop box 30 | TOCropViewCroppingStyleCircular // A fixed, circular crop box 31 | }; 32 | 33 | /** 34 | Preset values of the most common aspect ratios that can be used to quickly configure 35 | the crop view controller. 36 | */ 37 | typedef NS_ENUM(NSInteger, TOCropViewControllerAspectRatioPreset) { 38 | TOCropViewControllerAspectRatioPresetOriginal, 39 | TOCropViewControllerAspectRatioPresetSquare, 40 | TOCropViewControllerAspectRatioPreset3x2, 41 | TOCropViewControllerAspectRatioPreset5x3, 42 | TOCropViewControllerAspectRatioPreset4x3, 43 | TOCropViewControllerAspectRatioPreset5x4, 44 | TOCropViewControllerAspectRatioPreset7x5, 45 | TOCropViewControllerAspectRatioPreset16x9, 46 | TOCropViewControllerAspectRatioPresetCustom 47 | }; 48 | 49 | /** 50 | Whether the control toolbar is placed at the bottom or the top 51 | */ 52 | typedef NS_ENUM(NSInteger, TOCropViewControllerToolbarPosition) { 53 | TOCropViewControllerToolbarPositionBottom, // Bar is placed along the bottom in portrait 54 | TOCropViewControllerToolbarPositionTop // Bar is placed along the top in portrait (Respects the status bar) 55 | }; 56 | 57 | static inline NSBundle *TO_CROP_VIEW_RESOURCE_BUNDLE_FOR_OBJECT(NSObject *object) { 58 | #if SWIFT_PACKAGE 59 | // SPM is supposed to support the keyword SWIFTPM_MODULE_BUNDLE 60 | // but I can't figure out how to make it work, so doing it manually 61 | NSString *bundleName = @"TOCropViewController_TOCropViewController"; 62 | #else 63 | NSString *bundleName = @"TOCropViewControllerBundle"; 64 | #endif 65 | NSBundle *resourceBundle = nil; 66 | NSBundle *classBundle = [NSBundle bundleForClass:object.class]; 67 | NSURL *resourceBundleURL = [classBundle URLForResource:bundleName withExtension:@"bundle"]; 68 | if (resourceBundleURL) { 69 | resourceBundle = [[NSBundle alloc] initWithURL:resourceBundleURL]; 70 | #ifndef NDEBUG 71 | if (resourceBundle == nil) { 72 | @throw [[NSException alloc] initWithName:@"BundleAccessor" reason:[NSString stringWithFormat:@"unable to find bundle named %@", bundleName] userInfo:nil]; 73 | } 74 | #endif 75 | } else { 76 | resourceBundle = classBundle; 77 | } 78 | return resourceBundle; 79 | } 80 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Models/TOActivityCroppedImageProvider.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOActivityCroppedImageProvider.h 3 | // 4 | // Copyright 2015-2024 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import 24 | 25 | NS_ASSUME_NONNULL_BEGIN 26 | 27 | @interface TOActivityCroppedImageProvider : UIActivityItemProvider 28 | 29 | @property (nonnull, nonatomic, readonly) UIImage *image; 30 | @property (nonatomic, readonly) CGRect cropFrame; 31 | @property (nonatomic, readonly) NSInteger angle; 32 | @property (nonatomic, readonly) BOOL circular; 33 | 34 | - (nonnull instancetype)initWithImage:(nonnull UIImage *)image cropFrame:(CGRect)cropFrame angle:(NSInteger)angle circular:(BOOL)circular; 35 | 36 | @end 37 | 38 | NS_ASSUME_NONNULL_END 39 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Models/TOActivityCroppedImageProvider.m: -------------------------------------------------------------------------------- 1 | // 2 | // TOActivityCroppedImageProvider.m 3 | // 4 | // Copyright 2015-2024 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import "TOActivityCroppedImageProvider.h" 24 | #import "UIImage+CropRotate.h" 25 | 26 | @interface TOActivityCroppedImageProvider () 27 | 28 | @property (nonatomic, strong, readwrite) UIImage *image; 29 | @property (nonatomic, assign, readwrite) CGRect cropFrame; 30 | @property (nonatomic, assign, readwrite) NSInteger angle; 31 | @property (nonatomic, assign, readwrite) BOOL circular; 32 | 33 | @property (atomic, strong) UIImage *croppedImage; 34 | 35 | @end 36 | 37 | @implementation TOActivityCroppedImageProvider 38 | 39 | - (instancetype)initWithImage:(UIImage *)image cropFrame:(CGRect)cropFrame angle:(NSInteger)angle circular:(BOOL)circular 40 | { 41 | if (self = [super initWithPlaceholderItem:[UIImage new]]) { 42 | _image = image; 43 | _cropFrame = cropFrame; 44 | _angle = angle; 45 | _circular = circular; 46 | } 47 | 48 | return self; 49 | } 50 | 51 | #pragma mark - UIActivity Protocols - 52 | - (id)activityViewControllerPlaceholderItem:(UIActivityViewController *)activityViewController 53 | { 54 | return [[UIImage alloc] init]; 55 | } 56 | 57 | - (id)activityViewController:(UIActivityViewController *)activityViewController itemForActivityType:(NSString *)activityType 58 | { 59 | return self.croppedImage; 60 | } 61 | 62 | #pragma mark - Image Generation - 63 | - (id)item 64 | { 65 | //If the user didn't touch the image, just forward along the original 66 | if (self.angle == 0 && CGRectEqualToRect(self.cropFrame, (CGRect){CGPointZero, self.image.size})) { 67 | self.croppedImage = self.image; 68 | return self.croppedImage; 69 | } 70 | 71 | UIImage *image = [self.image croppedImageWithFrame:self.cropFrame angle:self.angle circularClip:self.circular]; 72 | self.croppedImage = image; 73 | return self.croppedImage; 74 | } 75 | 76 | @end 77 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Models/TOCropViewControllerTransitioning.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOCropViewControllerTransitioning.h 3 | // 4 | // Copyright 2015-2024 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import 24 | #import 25 | 26 | NS_ASSUME_NONNULL_BEGIN 27 | 28 | @interface TOCropViewControllerTransitioning : NSObject 29 | 30 | /* State Tracking */ 31 | @property (nonatomic, assign) BOOL isDismissing; // Whether this animation is presenting or dismissing 32 | @property (nullable, nonatomic, strong) UIImage *image; // The image that will be used in this animation 33 | 34 | /* Destination/Origin points */ 35 | @property (nullable, nonatomic, strong) UIView *fromView; // The origin view who's frame the image will be animated from 36 | @property (nullable, nonatomic, strong) UIView *toView; // The destination view who's frame the image will animate to 37 | 38 | @property (nonatomic, assign) CGRect fromFrame; // An origin frame that the image will be animated from 39 | @property (nonatomic, assign) CGRect toFrame; // A destination frame the image will aniamte to 40 | 41 | /* A block called just before the transition to perform any last-second UI configuration */ 42 | @property (nullable, nonatomic, copy) void (^prepareForTransitionHandler)(void); 43 | 44 | /* Empties all of the properties in this object */ 45 | - (void)reset; 46 | 47 | @end 48 | 49 | NS_ASSUME_NONNULL_END 50 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Models/TOCropViewControllerTransitioning.m: -------------------------------------------------------------------------------- 1 | // 2 | // TOCropViewControllerTransitioning.m 3 | // 4 | // Copyright 2015-2024 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import "TOCropViewControllerTransitioning.h" 24 | #import 25 | 26 | @implementation TOCropViewControllerTransitioning 27 | 28 | - (NSTimeInterval)transitionDuration:(id )transitionContext 29 | { 30 | return 0.45f; 31 | } 32 | 33 | - (void)animateTransition:(id )transitionContext 34 | { 35 | // Get the master view where the animation takes place 36 | UIView *containerView = [transitionContext containerView]; 37 | 38 | // Get the origin/destination view controllers 39 | UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey]; 40 | UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey]; 41 | 42 | // Work out which one is the crop view controller 43 | UIViewController *cropViewController = (self.isDismissing == NO) ? toViewController : fromViewController; 44 | UIViewController *previousController = (self.isDismissing == NO) ? fromViewController : toViewController; 45 | 46 | // Just in case, match up the frame sizes 47 | cropViewController.view.frame = containerView.bounds; 48 | if (self.isDismissing) { 49 | previousController.view.frame = containerView.bounds; 50 | } 51 | 52 | // Add the view layers beforehand as this will trigger the initial sets of layouts 53 | if (self.isDismissing == NO) { 54 | [containerView addSubview:cropViewController.view]; 55 | 56 | //Force a relayout now that the view is in the view hierarchy (so things like the safe area insets are now valid) 57 | [cropViewController viewDidLayoutSubviews]; 58 | } 59 | else { 60 | [containerView insertSubview:previousController.view belowSubview:cropViewController.view]; 61 | } 62 | 63 | // Perform any last UI updates now so we can potentially factor them into our calculations, but after 64 | // the container views have been set up 65 | if (self.prepareForTransitionHandler) { 66 | self.prepareForTransitionHandler(); 67 | } 68 | 69 | // If origin/destination views were supplied, use them to supplant the 70 | // frames 71 | if (!self.isDismissing && self.fromView) { 72 | self.fromFrame = [self.fromView.superview convertRect:self.fromView.frame toView:containerView]; 73 | } 74 | else if (self.isDismissing && self.toView) { 75 | self.toFrame = [self.toView.superview convertRect:self.toView.frame toView:containerView]; 76 | } 77 | 78 | UIImageView *imageView = nil; 79 | if ((self.isDismissing && !CGRectIsEmpty(self.toFrame)) || (!self.isDismissing && !CGRectIsEmpty(self.fromFrame))) { 80 | imageView = [[UIImageView alloc] initWithImage:self.image]; 81 | imageView.frame = self.fromFrame; 82 | [containerView addSubview:imageView]; 83 | 84 | if (@available(iOS 11.0, *)) { 85 | imageView.accessibilityIgnoresInvertColors = YES; 86 | } 87 | } 88 | 89 | cropViewController.view.alpha = (self.isDismissing ? 1.0f : 0.0f); 90 | if (imageView) { 91 | [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0f usingSpringWithDamping:1.0f initialSpringVelocity:0.7f options:0 animations:^{ 92 | imageView.frame = self.toFrame; 93 | } completion:^(BOOL complete) { 94 | [UIView animateWithDuration:0.25f animations:^{ 95 | imageView.alpha = 0.0f; 96 | }completion:^(BOOL complete) { 97 | [imageView removeFromSuperview]; 98 | }]; 99 | }]; 100 | } 101 | 102 | [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{ 103 | cropViewController.view.alpha = (self.isDismissing ? 0.0f : 1.0f); 104 | } completion:^(BOOL complete) { 105 | [self reset]; 106 | [transitionContext completeTransition:![transitionContext transitionWasCancelled]]; 107 | }]; 108 | } 109 | 110 | - (void)reset 111 | { 112 | self.image = nil; 113 | self.toView = nil; 114 | self.fromView = nil; 115 | self.fromFrame = CGRectZero; 116 | self.toFrame = CGRectZero; 117 | self.prepareForTransitionHandler = nil; 118 | } 119 | 120 | @end 121 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Models/TOCroppedImageAttributes.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOCroppedImageAttributes.h 3 | // 4 | // Copyright 2015-2024 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import 24 | #import 25 | 26 | NS_ASSUME_NONNULL_BEGIN 27 | 28 | @interface TOCroppedImageAttributes : NSObject 29 | 30 | @property (nonatomic, readonly) NSInteger angle; 31 | @property (nonatomic, readonly) CGRect croppedFrame; 32 | @property (nonatomic, readonly) CGSize originalImageSize; 33 | 34 | - (instancetype)initWithCroppedFrame:(CGRect)croppedFrame angle:(NSInteger)angle originalImageSize:(CGSize)originalSize; 35 | 36 | @end 37 | 38 | NS_ASSUME_NONNULL_END 39 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Models/TOCroppedImageAttributes.m: -------------------------------------------------------------------------------- 1 | // 2 | // TOCroppedImageAttributes.m 3 | // 4 | // Copyright 2015-2024 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import "TOCroppedImageAttributes.h" 24 | 25 | @interface TOCroppedImageAttributes () 26 | 27 | @property (nonatomic, assign, readwrite) NSInteger angle; 28 | @property (nonatomic, assign, readwrite) CGRect croppedFrame; 29 | @property (nonatomic, assign, readwrite) CGSize originalImageSize; 30 | 31 | @end 32 | 33 | @implementation TOCroppedImageAttributes 34 | 35 | - (instancetype)initWithCroppedFrame:(CGRect)croppedFrame angle:(NSInteger)angle originalImageSize:(CGSize)originalSize 36 | { 37 | if (self = [super init]) { 38 | _angle = angle; 39 | _croppedFrame = croppedFrame; 40 | _originalImageSize = originalSize; 41 | } 42 | 43 | return self; 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Resources/Base.lproj/TOCropViewControllerLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Done" = "Done"; 2 | "Cancel" = "Cancel"; 3 | "Reset" = "Reset"; 4 | "Original" = "Original"; 5 | "Square" = "Square"; 6 | "Delete Changes" = "Delete Changes"; 7 | "Yes" = "Yes"; 8 | "No" = "No"; 9 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Resources/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyTracking 6 | 7 | NSPrivacyTrackingDomains 8 | 9 | NSPrivacyCollectedDataTypes 10 | 11 | NSPrivacyAccessedAPITypes 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Resources/ar.lproj/TOCropViewControllerLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Done" = "تم"; 2 | "Cancel" = "إلغاء"; 3 | "Reset" = "إعادة تعيين"; 4 | "Original" = "أصلي"; 5 | "Square" = "مربع"; 6 | "Delete Changes" = "حذف التغييرات"; 7 | "Yes" = "نعم"; 8 | "No" = "لا"; 9 | 10 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Resources/ca.lproj/TOCropViewControllerLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Done" = "Fet"; 2 | "Cancel" = "Cancel·lar"; 3 | "Reset" = "Restablir"; 4 | "Original" = "Original"; 5 | "Square" = "Quadrat"; 6 | "Delete Changes" = "Esborrar Canvis"; 7 | "Yes" = "Si"; 8 | "No" = "No"; 9 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Resources/cs.lproj/TOCropViewControllerLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Done" = "Hotovo"; 2 | "Cancel" = "Zrušit"; 3 | "Reset" = "Reset"; 4 | "Original" = "Originál"; 5 | "Square" = "Čtverec"; 6 | "Delete Changes" = "Smazat změny"; 7 | "Yes" = "Ano"; 8 | "No" = "Ne"; 9 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Resources/da-DK.lproj/TOCropViewControllerLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Done" = "OK"; 2 | "Cancel" = "Annuller"; 3 | "Reset" = "Nulstil"; 4 | "Original" = "Original"; 5 | "Square" = "Firkantet"; 6 | "Delete Changes" = "Slet ændringer"; 7 | "Yes" = "Ja"; 8 | "No" = "Nej"; 9 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Resources/de.lproj/TOCropViewControllerLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Done" = "Fertig"; 2 | "Cancel" = "Abbrechen"; 3 | "Reset" = "Zurücksetzen"; 4 | "Original" = "Original"; 5 | "Square" = "Quadrat"; 6 | "Delete Changes" = "Änderungen löschen"; 7 | "Yes" = "Ja"; 8 | "No" = "Nein"; 9 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Resources/en.lproj/TOCropViewControllerLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Done" = "Done"; 2 | "Cancel" = "Cancel"; 3 | "Reset" = "Reset"; 4 | "Original" = "Original"; 5 | "Square" = "Square"; 6 | "Delete Changes" = "Delete Changes"; 7 | "Yes" = "Yes"; 8 | "No" = "No"; 9 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Resources/es.lproj/TOCropViewControllerLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Done" = "Aceptar"; 2 | "Cancel" = "Cancelar"; 3 | "Reset" = "Cambiar"; 4 | "Original" = "Original"; 5 | "Square" = "Cuadrada"; 6 | "Delete Changes" = "Eliminar cambios"; 7 | "Yes" = "Sí"; 8 | "No" = "No"; 9 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Resources/fa-IR.lproj/TOCropViewControllerLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Done" = "انجام شد"; 2 | "Cancel" = "انصراف"; 3 | "Reset" = "بازنشانی"; 4 | "Original" = "اصلی"; 5 | "Square" = "مربع"; 6 | "Delete Changes" = "حذف تغییرات"; 7 | "Yes" = "آری"; 8 | "No" = "نه"; 9 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Resources/fa.lproj/TOCropViewControllerLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Done" = "انجام شد"; 2 | "Cancel" = "انصراف"; 3 | "Reset" = "بازنشانی"; 4 | "Original" = "اصلی"; 5 | "Square" = "مربع"; 6 | "Delete Changes" = "حذف تغییرات"; 7 | "Yes" = "آری"; 8 | "No" = "نه"; 9 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Resources/fi.lproj/TOCropViewControllerLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Done" = "Valmis"; 2 | "Cancel" = "Kumoa"; 3 | "Reset" = "Palauta"; 4 | "Original" = "Alkuperäinen"; 5 | "Square" = "Neliö"; 6 | "Delete Changes" = "Peru muutokset"; 7 | "Yes" = "Kyllä"; 8 | "No" = "Ei"; 9 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Resources/fr.lproj/TOCropViewControllerLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Done" = "OK"; 2 | "Cancel" = "Annuler"; 3 | "Reset" = "Réinitialiser"; 4 | "Original" = "D’origine"; 5 | "Square" = "Carré"; 6 | "Delete Changes" = "Supprimer les modifications"; 7 | "Yes" = "Oui"; 8 | "No" = "Non"; 9 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Resources/hu.lproj/TOCropViewControllerLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Done" = "Kész"; 2 | "Cancel" = "Mégse"; 3 | "Reset" = "Visszaállítás"; 4 | "Original" = "Eredeti"; 5 | "Square" = "Négyzet"; 6 | "Delete Changes" = "Módosítások törlése"; 7 | "Yes" = "Igen"; 8 | "No" = "Nem"; 9 | 10 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Resources/id.lproj/TOCropViewControllerLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Done" = "Selesai"; 2 | "Cancel" = "Batalkan"; 3 | "Reset" = "Atur Ulang"; 4 | "Original" = "Asli"; 5 | "Square" = "Persegi"; 6 | "Delete Changes" = "Hapus Perubahan"; 7 | "Yes" = "Ya"; 8 | "No" = "Tidak"; 9 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Resources/it.lproj/TOCropViewControllerLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Done" = "Fatto"; 2 | "Cancel" = "Annulla"; 3 | "Reset" = "Ripristina"; 4 | "Original" = "Originale"; 5 | "Square" = "Quadrato"; 6 | "Delete Changes" = "Elimina modifiche"; 7 | "Yes" = "Sì"; 8 | "No" = "No"; 9 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Resources/ja.lproj/TOCropViewControllerLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Done" = "完了"; 2 | "Cancel" = "キャンセル"; 3 | "Reset" = "リセット"; 4 | "Original" = "オリジナル"; 5 | "Square" = "スクエア"; 6 | "Delete Changes" = "変更を削除"; 7 | "Yes" = "はい"; 8 | "No" = "いいえ"; 9 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Resources/ko.lproj/TOCropViewControllerLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Done" = "완료"; 2 | "Cancel" = "취소"; 3 | "Reset" = "재설정"; 4 | "Original" = "원본"; 5 | "Square" = "정방형"; 6 | "Delete Changes" = "변경사항 삭제"; 7 | "Yes" = "예"; 8 | "No" = "아니요"; 9 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Resources/ms.lproj/TOCropViewControllerLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Done" = "Selesai"; 2 | "Cancel" = "Batal"; 3 | "Reset" = "Reset"; 4 | "Original" = "Asal"; 5 | "Square" = "Segi empat"; 6 | "Delete Changes" = "Padam Perubahan"; 7 | "Yes" = "Ya"; 8 | "No" = "Tidak"; 9 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Resources/nl.lproj/TOCropViewControllerLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Done" = "Gereed"; 2 | "Cancel" = "Annuleer"; 3 | "Reset" = "Herstel"; 4 | "Original" = "Origineel"; 5 | "Square" = "Vierkant"; 6 | "Delete Changes" = "Wis wijzigingen"; 7 | "Yes" = "Ja"; 8 | "No" = "Nee"; 9 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Resources/pl.lproj/TOCropViewControllerLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Done" = "Gotowe"; 2 | "Cancel" = "Anuluj"; 3 | "Reset" = "Wyzeruj"; 4 | "Original" = "Orygin."; 5 | "Square" = "Kwadrat"; 6 | "Delete Changes" = "Usuń zmiany"; 7 | "Yes" = "Tak"; 8 | "No" = "Nie"; 9 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Resources/pt-BR.lproj/TOCropViewControllerLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Done" = "OK"; 2 | "Cancel" = "Cancelar"; 3 | "Reset" = "Redefinir"; 4 | "Original" = "Original"; 5 | "Square" = "Quadrada"; 6 | "Delete Changes" = "Apagar Alterações"; 7 | "Yes" = "Sim"; 8 | "No" = "Não"; 9 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Resources/pt.lproj/TOCropViewControllerLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Done" = "OK"; 2 | "Cancel" = "Cancelar"; 3 | "Reset" = "Redefinir"; 4 | "Original" = "Original"; 5 | "Square" = "Quadrada"; 6 | "Delete Changes" = "Apagar Alterações"; 7 | "Yes" = "Sim"; 8 | "No" = "Não"; 9 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Resources/ro.lproj/TOCropViewControllerLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Done" = "Gata"; 2 | "Cancel" = "Anulare"; 3 | "Reset" = "Resetare"; 4 | "Original" = "Original"; 5 | "Square" = "Patrat"; 6 | "Delete Changes" = "Ștergeți modificările"; 7 | "Yes" = "Da"; 8 | "No" = "Nu"; 9 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Resources/ru.lproj/TOCropViewControllerLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Done" = "Готово"; 2 | "Cancel" = "Отменить"; 3 | "Reset" = "Сбросить"; 4 | "Original" = "Оригинал"; 5 | "Square" = "Квадрат"; 6 | "Delete Changes" = "Удалить изменения"; 7 | "Yes" = "Да"; 8 | "No" = "Нет"; 9 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Resources/sk.lproj/TOCropViewControllerLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Done" = "Hotovo"; 2 | "Cancel" = "Zrušiť"; 3 | "Reset" = "Reset"; 4 | "Original" = "Originál"; 5 | "Square" = "Štvorec"; 6 | "Delete Changes" = "Zmazať zmeny"; 7 | "Yes" = "Áno"; 8 | "No" = "Nie"; 9 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Resources/tr.lproj/TOCropViewControllerLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Done" = "Tamam"; 2 | "Cancel" = "Vazgeç"; 3 | "Reset" = "Sıfırla"; 4 | "Original" = "Orjinal"; 5 | "Square" = "Kare"; 6 | "Delete Changes" = "Değişiklikleri Sil"; 7 | "Yes" = "Evet"; 8 | "No" = "Hayır"; 9 | 10 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Resources/uk.lproj/TOCropViewControllerLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Done" = "Готово"; 2 | "Cancel" = "Скасувати"; 3 | "Reset" = "Скинути"; 4 | "Original" = "Оригінал"; 5 | "Square" = "Квадрат"; 6 | "Delete Changes" = "Видалити Зміни"; 7 | "Yes" = "Так"; 8 | "No" = "Ні"; 9 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Resources/vi.lproj/TOCropViewControllerLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Done" = "Xong"; 2 | "Cancel" = "Huỷ"; 3 | "Reset" = "Đặt lại"; 4 | "Original" = "Gốc"; 5 | "Square" = "Vuông"; 6 | "Delete Changes" = "Xóa Thay đổi"; 7 | "Yes" = "Có"; 8 | "No" = "Không"; 9 | 10 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Resources/zh-Hans.lproj/TOCropViewControllerLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Done" = "完成"; 2 | "Cancel" = "取消"; 3 | "Reset" = "重设"; 4 | "Original" = "原有"; 5 | "Square" = "正方形"; 6 | "Delete Changes" = "删除更改"; 7 | "Yes" = "是"; 8 | "No" = "否"; 9 | 10 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Resources/zh-Hant.lproj/TOCropViewControllerLocalizable.strings: -------------------------------------------------------------------------------- 1 | "Done" = "完成"; 2 | "Cancel" = "取消"; 3 | "Reset" = "重置"; 4 | "Original" = "原始檔"; 5 | "Square" = "正方形"; 6 | "Delete Changes" = "刪除更動"; 7 | 8 | "Yes" = "是"; 9 | "No" = "否"; 10 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Supporting/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Views/TOCropOverlayView.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOCropOverlayView.h 3 | // 4 | // Copyright 2015-2024 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import 24 | 25 | NS_ASSUME_NONNULL_BEGIN 26 | 27 | @interface TOCropOverlayView : UIView 28 | 29 | /** Hides the interior grid lines, sans animation. */ 30 | @property (nonatomic, assign) BOOL gridHidden; 31 | 32 | /** Add/Remove the interior horizontal grid lines. */ 33 | @property (nonatomic, assign) BOOL displayHorizontalGridLines; 34 | 35 | /** Add/Remove the interior vertical grid lines. */ 36 | @property (nonatomic, assign) BOOL displayVerticalGridLines; 37 | 38 | /** Shows and hides the interior grid lines with an optional crossfade animation. */ 39 | - (void)setGridHidden:(BOOL)hidden animated:(BOOL)animated; 40 | 41 | @end 42 | 43 | NS_ASSUME_NONNULL_END 44 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Views/TOCropOverlayView.m: -------------------------------------------------------------------------------- 1 | // 2 | // TOCropOverlayView.m 3 | // 4 | // Copyright 2015-2024 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import "TOCropOverlayView.h" 24 | 25 | static const CGFloat kTOCropOverLayerCornerWidth = 20.0f; 26 | 27 | @interface TOCropOverlayView () 28 | 29 | @property (nonatomic, strong) NSArray *horizontalGridLines; 30 | @property (nonatomic, strong) NSArray *verticalGridLines; 31 | 32 | @property (nonatomic, strong) NSArray *outerLineViews; //top, right, bottom, left 33 | 34 | @property (nonatomic, strong) NSArray *topLeftLineViews; //vertical, horizontal 35 | @property (nonatomic, strong) NSArray *bottomLeftLineViews; 36 | @property (nonatomic, strong) NSArray *bottomRightLineViews; 37 | @property (nonatomic, strong) NSArray *topRightLineViews; 38 | 39 | @end 40 | 41 | @implementation TOCropOverlayView 42 | 43 | - (instancetype)initWithFrame:(CGRect)frame 44 | { 45 | if (self = [super initWithFrame:frame]) { 46 | self.clipsToBounds = NO; 47 | [self setup]; 48 | } 49 | 50 | return self; 51 | } 52 | 53 | - (void)setup 54 | { 55 | UIView *(^newLineView)(void) = ^UIView *(void){ 56 | return [self createNewLineView]; 57 | }; 58 | 59 | _outerLineViews = @[newLineView(), newLineView(), newLineView(), newLineView()]; 60 | 61 | _topLeftLineViews = @[newLineView(), newLineView()]; 62 | _bottomLeftLineViews = @[newLineView(), newLineView()]; 63 | _topRightLineViews = @[newLineView(), newLineView()]; 64 | _bottomRightLineViews = @[newLineView(), newLineView()]; 65 | 66 | self.displayHorizontalGridLines = YES; 67 | self.displayVerticalGridLines = YES; 68 | } 69 | 70 | - (void)setFrame:(CGRect)frame 71 | { 72 | [super setFrame:frame]; 73 | if (_outerLineViews) { 74 | [self layoutLines]; 75 | } 76 | } 77 | 78 | - (void)didMoveToSuperview 79 | { 80 | [super didMoveToSuperview]; 81 | if (_outerLineViews) { 82 | [self layoutLines]; 83 | } 84 | } 85 | 86 | - (void)layoutLines 87 | { 88 | CGSize boundsSize = self.bounds.size; 89 | 90 | //border lines 91 | for (NSInteger i = 0; i < 4; i++) { 92 | UIView *lineView = self.outerLineViews[i]; 93 | 94 | CGRect frame = CGRectZero; 95 | switch (i) { 96 | case 0: frame = (CGRect){-1.0f,-1.0f,boundsSize.width+2.0f, 1.0f}; break; //top 97 | case 1: frame = (CGRect){boundsSize.width,0.0f,1.0f,boundsSize.height}; break; //right 98 | case 2: frame = (CGRect){-1.0f,boundsSize.height,boundsSize.width+2.0f,1.0f}; break; //bottom 99 | case 3: frame = (CGRect){-1.0f,0,1.0f,boundsSize.height+1.0f}; break; //left 100 | } 101 | 102 | lineView.frame = frame; 103 | } 104 | 105 | //corner liness 106 | NSArray *cornerLines = @[self.topLeftLineViews, self.topRightLineViews, self.bottomRightLineViews, self.bottomLeftLineViews]; 107 | for (NSInteger i = 0; i < 4; i++) { 108 | NSArray *cornerLine = cornerLines[i]; 109 | 110 | CGRect verticalFrame = CGRectZero, horizontalFrame = CGRectZero; 111 | switch (i) { 112 | case 0: //top left 113 | verticalFrame = (CGRect){-3.0f,-3.0f,3.0f,kTOCropOverLayerCornerWidth+3.0f}; 114 | horizontalFrame = (CGRect){0,-3.0f,kTOCropOverLayerCornerWidth,3.0f}; 115 | break; 116 | case 1: //top right 117 | verticalFrame = (CGRect){boundsSize.width,-3.0f,3.0f,kTOCropOverLayerCornerWidth+3.0f}; 118 | horizontalFrame = (CGRect){boundsSize.width-kTOCropOverLayerCornerWidth,-3.0f,kTOCropOverLayerCornerWidth,3.0f}; 119 | break; 120 | case 2: //bottom right 121 | verticalFrame = (CGRect){boundsSize.width,boundsSize.height-kTOCropOverLayerCornerWidth,3.0f,kTOCropOverLayerCornerWidth+3.0f}; 122 | horizontalFrame = (CGRect){boundsSize.width-kTOCropOverLayerCornerWidth,boundsSize.height,kTOCropOverLayerCornerWidth,3.0f}; 123 | break; 124 | case 3: //bottom left 125 | verticalFrame = (CGRect){-3.0f,boundsSize.height-kTOCropOverLayerCornerWidth,3.0f,kTOCropOverLayerCornerWidth}; 126 | horizontalFrame = (CGRect){-3.0f,boundsSize.height,kTOCropOverLayerCornerWidth+3.0f,3.0f}; 127 | break; 128 | } 129 | 130 | [cornerLine[0] setFrame:verticalFrame]; 131 | [cornerLine[1] setFrame:horizontalFrame]; 132 | } 133 | 134 | //grid lines - horizontal 135 | CGFloat thickness = 1.0f / self.traitCollection.displayScale; 136 | NSInteger numberOfLines = self.horizontalGridLines.count; 137 | CGFloat padding = (CGRectGetHeight(self.bounds) - (thickness*numberOfLines)) / (numberOfLines + 1); 138 | for (NSInteger i = 0; i < numberOfLines; i++) { 139 | UIView *lineView = self.horizontalGridLines[i]; 140 | CGRect frame = CGRectZero; 141 | frame.size.height = thickness; 142 | frame.size.width = CGRectGetWidth(self.bounds); 143 | frame.origin.y = (padding * (i+1)) + (thickness * i); 144 | lineView.frame = frame; 145 | } 146 | 147 | //grid lines - vertical 148 | numberOfLines = self.verticalGridLines.count; 149 | padding = (CGRectGetWidth(self.bounds) - (thickness*numberOfLines)) / (numberOfLines + 1); 150 | for (NSInteger i = 0; i < numberOfLines; i++) { 151 | UIView *lineView = self.verticalGridLines[i]; 152 | CGRect frame = CGRectZero; 153 | frame.size.width = thickness; 154 | frame.size.height = CGRectGetHeight(self.bounds); 155 | frame.origin.x = (padding * (i+1)) + (thickness * i); 156 | lineView.frame = frame; 157 | } 158 | } 159 | 160 | - (void)setGridHidden:(BOOL)hidden animated:(BOOL)animated 161 | { 162 | _gridHidden = hidden; 163 | 164 | if (animated == NO) { 165 | for (UIView *lineView in self.horizontalGridLines) { 166 | lineView.alpha = hidden ? 0.0f : 1.0f; 167 | } 168 | 169 | for (UIView *lineView in self.verticalGridLines) { 170 | lineView.alpha = hidden ? 0.0f : 1.0f; 171 | } 172 | 173 | return; 174 | } 175 | 176 | [UIView animateWithDuration:hidden?0.35f:0.2f animations:^{ 177 | for (UIView *lineView in self.horizontalGridLines) 178 | lineView.alpha = hidden ? 0.0f : 1.0f; 179 | 180 | for (UIView *lineView in self.verticalGridLines) 181 | lineView.alpha = hidden ? 0.0f : 1.0f; 182 | }]; 183 | } 184 | 185 | #pragma mark - Property methods 186 | 187 | - (void)setDisplayHorizontalGridLines:(BOOL)displayHorizontalGridLines { 188 | _displayHorizontalGridLines = displayHorizontalGridLines; 189 | 190 | [self.horizontalGridLines enumerateObjectsUsingBlock:^(UIView *__nonnull lineView, NSUInteger idx, BOOL * __nonnull stop) { 191 | [lineView removeFromSuperview]; 192 | }]; 193 | 194 | if (_displayHorizontalGridLines) { 195 | self.horizontalGridLines = @[[self createNewLineView], [self createNewLineView]]; 196 | } else { 197 | self.horizontalGridLines = @[]; 198 | } 199 | [self setNeedsDisplay]; 200 | } 201 | 202 | - (void)setDisplayVerticalGridLines:(BOOL)displayVerticalGridLines { 203 | _displayVerticalGridLines = displayVerticalGridLines; 204 | 205 | [self.verticalGridLines enumerateObjectsUsingBlock:^(UIView *__nonnull lineView, NSUInteger idx, BOOL * __nonnull stop) { 206 | [lineView removeFromSuperview]; 207 | }]; 208 | 209 | if (_displayVerticalGridLines) { 210 | self.verticalGridLines = @[[self createNewLineView], [self createNewLineView]]; 211 | } else { 212 | self.verticalGridLines = @[]; 213 | } 214 | [self setNeedsDisplay]; 215 | } 216 | 217 | - (void)setGridHidden:(BOOL)gridHidden 218 | { 219 | [self setGridHidden:gridHidden animated:NO]; 220 | } 221 | 222 | #pragma mark - Private methods 223 | 224 | - (nonnull UIView *)createNewLineView { 225 | UIView *newLine = [[UIView alloc] initWithFrame:CGRectZero]; 226 | newLine.backgroundColor = [UIColor whiteColor]; 227 | [self addSubview:newLine]; 228 | return newLine; 229 | } 230 | 231 | @end 232 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Views/TOCropScrollView.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOCropScrollView 3 | // 4 | // Copyright 2015-2024 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import 24 | 25 | NS_ASSUME_NONNULL_BEGIN 26 | 27 | /* 28 | Subclassing UIScrollView was necessary in order to directly capture 29 | touch events that weren't otherwise accessible via UIGestureRecognizer objects. 30 | */ 31 | @interface TOCropScrollView : UIScrollView 32 | 33 | @property (nullable, nonatomic, copy) void (^touchesBegan)(void); 34 | @property (nullable, nonatomic, copy) void (^touchesCancelled)(void); 35 | @property (nullable, nonatomic, copy) void (^touchesEnded)(void); 36 | 37 | @end 38 | 39 | NS_ASSUME_NONNULL_END 40 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Views/TOCropScrollView.m: -------------------------------------------------------------------------------- 1 | // 2 | // TOCropScrollView 3 | // 4 | // Copyright 2015-2024 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import "TOCropScrollView.h" 24 | 25 | @implementation TOCropScrollView 26 | 27 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 28 | { 29 | if (self.touchesBegan) 30 | self.touchesBegan(); 31 | 32 | [super touchesBegan:touches withEvent:event]; 33 | } 34 | 35 | - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event 36 | { 37 | if (self.touchesEnded) 38 | self.touchesEnded(); 39 | 40 | [super touchesEnded:touches withEvent:event]; 41 | } 42 | 43 | - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event 44 | { 45 | if (self.touchesCancelled) 46 | self.touchesCancelled(); 47 | 48 | [super touchesCancelled:touches withEvent:event]; 49 | } 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Views/TOCropToolbar.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOCropToolbar.h 3 | // 4 | // Copyright 2015-2024 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import 24 | 25 | #if !__has_include() 26 | #import "TOCropViewConstants.h" 27 | #else 28 | #import 29 | #endif 30 | 31 | NS_ASSUME_NONNULL_BEGIN 32 | 33 | @interface TOCropToolbar : UIView 34 | 35 | /* In horizontal mode, offsets all of the buttons vertically by height of status bar. */ 36 | @property (nonatomic, assign) CGFloat statusBarHeightInset; 37 | 38 | /* Set an inset that will expand the background view beyond the bounds. */ 39 | @property (nonatomic, assign) UIEdgeInsets backgroundViewOutsets; 40 | 41 | /* The 'Done' buttons to commit the crop. The text button is displayed 42 | in portrait mode and the icon one, in landscape. */ 43 | @property (nonatomic, strong, readonly) UIButton *doneTextButton; 44 | @property (nonatomic, strong, readonly) UIButton *doneIconButton; 45 | @property (nonatomic, copy) NSString *doneTextButtonTitle; 46 | @property (null_resettable, nonatomic, copy) UIColor *doneButtonColor; 47 | 48 | /* The 'Cancel' buttons to cancel the crop. The text button is displayed 49 | in portrait mode and the icon one, in landscape. */ 50 | @property (nonatomic, strong, readonly) UIButton *cancelTextButton; 51 | @property (nonatomic, strong, readonly) UIButton *cancelIconButton; 52 | @property (nonatomic, readonly) UIView *visibleCancelButton; 53 | @property (nonatomic, copy) NSString *cancelTextButtonTitle; 54 | @property (nullable, nonatomic, copy) UIColor *cancelButtonColor; 55 | 56 | @property (nonatomic, assign) BOOL showOnlyIcons; 57 | 58 | /* The cropper control buttons */ 59 | @property (nonatomic, strong, readonly) UIButton *rotateCounterclockwiseButton; 60 | @property (nonatomic, strong, readonly) UIButton *resetButton; 61 | @property (nonatomic, strong, readonly) UIButton *clampButton; 62 | @property (nullable, nonatomic, strong, readonly) UIButton *rotateClockwiseButton; 63 | 64 | @property (nonatomic, readonly) UIButton *rotateButton; // Points to `rotateCounterClockwiseButton` 65 | 66 | /* Button feedback handler blocks */ 67 | @property (nullable, nonatomic, copy) void (^cancelButtonTapped)(void); 68 | @property (nullable, nonatomic, copy) void (^doneButtonTapped)(void); 69 | @property (nullable, nonatomic, copy) void (^rotateCounterclockwiseButtonTapped)(void); 70 | @property (nullable, nonatomic, copy) void (^rotateClockwiseButtonTapped)(void); 71 | @property (nullable, nonatomic, copy) void (^clampButtonTapped)(void); 72 | @property (nullable, nonatomic, copy) void (^resetButtonTapped)(void); 73 | 74 | /* State management for the 'clamp' button */ 75 | @property (nonatomic, assign) BOOL clampButtonGlowing; 76 | @property (nonatomic, readonly) CGRect clampButtonFrame; 77 | 78 | /* Aspect ratio button visibility settings */ 79 | @property (nonatomic, assign) BOOL clampButtonHidden; 80 | @property (nonatomic, assign) BOOL rotateCounterclockwiseButtonHidden; 81 | @property (nonatomic, assign) BOOL rotateClockwiseButtonHidden; 82 | @property (nonatomic, assign) BOOL resetButtonHidden; 83 | @property (nonatomic, assign) BOOL doneButtonHidden; 84 | @property (nonatomic, assign) BOOL cancelButtonHidden; 85 | 86 | /* For languages like Arabic where they natively present content flipped from English */ 87 | @property (nonatomic, assign) BOOL reverseContentLayout; 88 | 89 | /* Enable the reset button */ 90 | @property (nonatomic, assign) BOOL resetButtonEnabled; 91 | 92 | /* Done button frame for popover controllers */ 93 | @property (nonatomic, readonly) CGRect doneButtonFrame; 94 | 95 | @end 96 | 97 | NS_ASSUME_NONNULL_END 98 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/Views/TOCropView.h: -------------------------------------------------------------------------------- 1 | // 2 | // TOCropView.h 3 | // 4 | // Copyright 2015-2024 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import 24 | 25 | #if !__has_include() 26 | #import "TOCropViewConstants.h" 27 | #else 28 | #import 29 | #endif 30 | 31 | @class TOCropOverlayView; 32 | @class TOCropView; 33 | 34 | NS_ASSUME_NONNULL_BEGIN 35 | 36 | @protocol TOCropViewDelegate 37 | 38 | - (void)cropViewDidBecomeResettable:(nonnull TOCropView *)cropView; 39 | - (void)cropViewDidBecomeNonResettable:(nonnull TOCropView *)cropView; 40 | 41 | @end 42 | 43 | @interface TOCropView : UIView 44 | 45 | /** 46 | The image that the crop view is displaying. This cannot be changed once the crop view is instantiated. 47 | */ 48 | @property (nonnull, nonatomic, strong, readonly) UIImage *image; 49 | 50 | /** 51 | The cropping style of the crop view (eg, rectangular or circular) 52 | */ 53 | @property (nonatomic, assign, readonly) TOCropViewCroppingStyle croppingStyle; 54 | 55 | /** 56 | A semi-transparent grey view, overlaid on top of the background image 57 | */ 58 | @property (nonatomic, strong, readonly) UIView *overlayView; 59 | 60 | /** 61 | A grid view overlaid on top of the foreground image view's container. 62 | */ 63 | @property (nonnull, nonatomic, strong, readonly) TOCropOverlayView *gridOverlayView; 64 | 65 | /** 66 | A container view that clips the a copy of the image so it appears over the dimming view 67 | */ 68 | @property (nonnull, nonatomic, readonly) UIView *foregroundContainerView; 69 | 70 | /** 71 | A delegate object that receives notifications from the crop view 72 | */ 73 | @property (nullable, nonatomic, weak) id delegate; 74 | 75 | /** 76 | If false, the user cannot resize the crop box frame using a pan gesture from a corner. 77 | Default vaue is YES. 78 | */ 79 | @property (nonatomic, assign) BOOL cropBoxResizeEnabled; 80 | 81 | /** 82 | Whether the user has manipulated the crop view to the point where it can be reset 83 | */ 84 | @property (nonatomic, readonly) BOOL canBeReset; 85 | 86 | /** 87 | The frame of the cropping box in the coordinate space of the crop view 88 | */ 89 | @property (nonatomic, readonly) CGRect cropBoxFrame; 90 | 91 | /** 92 | The frame of the entire image in the backing scroll view 93 | */ 94 | @property (nonatomic, readonly) CGRect imageViewFrame; 95 | 96 | /** 97 | Inset the workable region of the crop view in case in order to make space for accessory views 98 | */ 99 | @property (nonatomic, assign) UIEdgeInsets cropRegionInsets; 100 | 101 | /** 102 | Disable the dynamic translucency in order to smoothly relayout the view 103 | */ 104 | @property (nonatomic, assign) BOOL simpleRenderMode; 105 | 106 | /** 107 | When performing manual content layout (such as during screen rotation), disable any internal layout 108 | */ 109 | @property (nonatomic, assign) BOOL internalLayoutDisabled; 110 | 111 | /** 112 | A width x height ratio that the crop box will be rescaled to (eg 4:3 is {4.0f, 3.0f}) 113 | Setting it to CGSizeZero will reset the aspect ratio to the image's own ratio. 114 | */ 115 | @property (nonatomic, assign) CGSize aspectRatio; 116 | 117 | /** 118 | When the cropping box is locked to its current aspect ratio (But can still be resized) 119 | */ 120 | @property (nonatomic, assign) BOOL aspectRatioLockEnabled; 121 | 122 | /** 123 | If true, a custom aspect ratio is set, and the aspectRatioLockEnabled is set to YES, 124 | the crop box will swap it's dimensions depending on portrait or landscape sized images. 125 | This value also controls whether the dimensions can swap when the image is rotated. 126 | 127 | Default is NO. 128 | */ 129 | @property (nonatomic, assign) BOOL aspectRatioLockDimensionSwapEnabled; 130 | 131 | /** 132 | When the user taps 'reset', whether the aspect ratio will also be reset as well 133 | Default is YES 134 | */ 135 | @property (nonatomic, assign) BOOL resetAspectRatioEnabled; 136 | 137 | /** 138 | True when the height of the crop box is bigger than the width 139 | */ 140 | @property (nonatomic, readonly) BOOL cropBoxAspectRatioIsPortrait; 141 | 142 | /** 143 | The rotation angle of the crop view (Will always be negative as it rotates in a counter-clockwise direction) 144 | */ 145 | @property (nonatomic, assign) NSInteger angle; 146 | 147 | /** 148 | Hide all of the crop elements for transition animations 149 | */ 150 | @property (nonatomic, assign) BOOL croppingViewsHidden; 151 | 152 | /** 153 | In relation to the coordinate space of the image, the frame that the crop view is focusing on 154 | */ 155 | @property (nonatomic, assign) CGRect imageCropFrame; 156 | 157 | /** 158 | Set the grid overlay graphic to be hidden 159 | */ 160 | @property (nonatomic, assign) BOOL gridOverlayHidden; 161 | 162 | ///** 163 | // Paddings of the crop rectangle. Default to 14.0 164 | // */ 165 | @property (nonatomic) CGFloat cropViewPadding; 166 | 167 | /** 168 | Delay before crop frame is adjusted according new crop area. Default to 0.8 169 | */ 170 | @property (nonatomic) NSTimeInterval cropAdjustingDelay; 171 | 172 | /** 173 | The minimum croping aspect ratio. If set, user is prevented from setting cropping 174 | rectangle to lower aspect ratio than defined by the parameter. 175 | */ 176 | @property (nonatomic, assign) CGFloat minimumAspectRatio; 177 | 178 | /** 179 | The maximum scale that user can apply to image by pinching to zoom. Small values 180 | are only recomended with aspectRatioLockEnabled set to true. Default to 15.0 181 | */ 182 | @property (nonatomic, assign) CGFloat maximumZoomScale; 183 | 184 | /** 185 | Always show the cropping grid lines, even when the user isn't interacting. 186 | This also disables the fading animation. 187 | (Default is NO) 188 | */ 189 | @property (nonatomic, assign) BOOL alwaysShowCroppingGrid; 190 | 191 | /** 192 | Permanently hides the translucency effect covering the outside bounds of the 193 | crop box. (Default is NO) 194 | */ 195 | @property (nonatomic, assign) BOOL translucencyAlwaysHidden; 196 | 197 | ///* 198 | // if YES it will always show grid 199 | // if NO it will never show grid 200 | // NOTE : Do not use this method if you want to keep grid hide/show animation 201 | // */ 202 | //- (void)setAlwaysShowGrid:(BOOL)showGrid; 203 | // 204 | ///* 205 | // if YES it will disable translucency effect 206 | // */ 207 | //- (void)setTranslucencyOff:(BOOL)disableTranslucency; 208 | 209 | 210 | /** 211 | Create a default instance of the crop view with the supplied image 212 | */ 213 | - (nonnull instancetype)initWithImage:(nonnull UIImage *)image; 214 | 215 | /** 216 | Create a new instance of the crop view with the specified image and cropping 217 | */ 218 | - (nonnull instancetype)initWithCroppingStyle:(TOCropViewCroppingStyle)style image:(nonnull UIImage *)image; 219 | 220 | /** 221 | Performs the initial set up, including laying out the image and applying any restore properties. 222 | This should be called once the crop view has been added to a parent that is in its final layout frame. 223 | */ 224 | - (void)performInitialSetup; 225 | 226 | /** 227 | When performing large size transitions (eg, orientation rotation), 228 | set simple mode to YES to temporarily graphically heavy effects like translucency. 229 | 230 | @param simpleMode Whether simple mode is enabled or not 231 | 232 | */ 233 | - (void)setSimpleRenderMode:(BOOL)simpleMode animated:(BOOL)animated; 234 | 235 | /** 236 | When performing a screen rotation that will change the size of the scroll view, this takes 237 | a snapshot of all of the scroll view data before it gets manipulated by iOS. 238 | Please call this in your view controller, before the rotation animation block is committed. 239 | */ 240 | - (void)prepareforRotation; 241 | 242 | /** 243 | Performs the realignment of the crop view while the screen is rotating. 244 | Please call this inside your view controller's screen rotation animation block. 245 | */ 246 | - (void)performRelayoutForRotation; 247 | 248 | /** 249 | Reset the crop box and zoom scale back to the initial layout 250 | 251 | @param animated The reset is animated 252 | */ 253 | - (void)resetLayoutToDefaultAnimated:(BOOL)animated; 254 | 255 | /** 256 | Changes the aspect ratio of the crop box to match the one specified 257 | 258 | @param aspectRatio The aspect ratio (For example 16:9 is 16.0f/9.0f). 'CGSizeZero' will reset it to the image's own ratio 259 | @param animated Whether the locking effect is animated 260 | */ 261 | - (void)setAspectRatio:(CGSize)aspectRatio animated:(BOOL)animated; 262 | 263 | /** 264 | Rotates the entire canvas to a 90-degree angle. The default rotation is counterclockwise. 265 | 266 | @param animated Whether the transition is animated 267 | */ 268 | - (void)rotateImageNinetyDegreesAnimated:(BOOL)animated; 269 | 270 | /** 271 | Rotates the entire canvas to a 90-degree angle 272 | 273 | @param animated Whether the transition is animated 274 | @param clockwise Whether the rotation is clockwise. Passing 'NO' means counterclockwise 275 | */ 276 | - (void)rotateImageNinetyDegreesAnimated:(BOOL)animated clockwise:(BOOL)clockwise; 277 | 278 | /** 279 | Animate the grid overlay graphic to be visible 280 | */ 281 | - (void)setGridOverlayHidden:(BOOL)gridOverlayHidden animated:(BOOL)animated; 282 | 283 | /** 284 | Animate the cropping component views to become visible 285 | */ 286 | - (void)setCroppingViewsHidden:(BOOL)hidden animated:(BOOL)animated; 287 | 288 | /** 289 | Animate the background image view to become visible 290 | */ 291 | - (void)setBackgroundImageViewHidden:(BOOL)hidden animated:(BOOL)animated; 292 | 293 | /** 294 | When triggered, the crop view will perform a relayout to ensure the crop box 295 | fills the entire crop view region 296 | */ 297 | - (void)moveCroppedContentToCenterAnimated:(BOOL)animated; 298 | 299 | @end 300 | 301 | NS_ASSUME_NONNULL_END 302 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/include/TOActivityCroppedImageProvider.h: -------------------------------------------------------------------------------- 1 | ../Models/TOActivityCroppedImageProvider.h -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/include/TOCropOverlayView.h: -------------------------------------------------------------------------------- 1 | ../Views/TOCropOverlayView.h -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/include/TOCropScrollView.h: -------------------------------------------------------------------------------- 1 | ../Views/TOCropScrollView.h -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/include/TOCropToolbar.h: -------------------------------------------------------------------------------- 1 | ../Views/TOCropToolbar.h -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/include/TOCropView.h: -------------------------------------------------------------------------------- 1 | ../Views/TOCropView.h -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/include/TOCropViewConstants.h: -------------------------------------------------------------------------------- 1 | ../Constants/TOCropViewConstants.h -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/include/TOCropViewController.h: -------------------------------------------------------------------------------- 1 | ../TOCropViewController.h -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/include/TOCropViewControllerTransitioning.h: -------------------------------------------------------------------------------- 1 | ../Models/TOCropViewControllerTransitioning.h -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/include/TOCroppedImageAttributes.h: -------------------------------------------------------------------------------- 1 | ../Models/TOCroppedImageAttributes.h -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/include/UIImage+CropRotate.h: -------------------------------------------------------------------------------- 1 | ../Categories/UIImage+CropRotate.h -------------------------------------------------------------------------------- /Objective-C/TOCropViewController/include/module.modulemap: -------------------------------------------------------------------------------- 1 | module TOCropViewController { 2 | umbrella ".." 3 | export * 4 | } 5 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewControllerExample-Extension/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | TOCropViewControllerExample 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | XPC! 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | NSExtension 24 | 25 | NSExtensionAttributes 26 | 27 | NSExtensionActivationRule 28 | 29 | NSExtensionActivationSupportsImageWithMaxCount 30 | 1 31 | NSExtensionActivationSupportsMovieWithMaxCount 32 | 0 33 | NSExtensionActivationSupportsText 34 | 35 | NSExtensionActivationSupportsWebURLWithMaxCount 36 | 1 37 | 38 | 39 | NSExtensionPointIdentifier 40 | com.apple.share-services 41 | NSExtensionPrincipalClass 42 | ShareViewController 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewControllerExample-Extension/ShareViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ShareViewController.h 3 | // TOCropViewController-ShareExtension 4 | // 5 | // Created by Shardul Patel on 27/08/17. 6 | // Copyright © 2017 Tim Oliver. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ShareViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewControllerExample-Extension/ShareViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ShareViewController.m 3 | // TOCropViewController-ShareExtension 4 | // 5 | // Created by Shardul Patel on 27/08/17. 6 | // Copyright © 2017 Tim Oliver. All rights reserved. 7 | // 8 | 9 | #import "ShareViewController.h" 10 | #import "ViewController.h" 11 | 12 | @implementation ShareViewController 13 | 14 | - (void) viewDidLoad { 15 | [super viewDidLoad]; 16 | 17 | // Instantiate and create the initial view controller from the main example app 18 | UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil]; 19 | UINavigationController *viewController = [storyboard instantiateInitialViewController]; 20 | [self presentViewController:viewController animated:YES completion:nil]; 21 | } 22 | 23 | - (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion 24 | { 25 | [self.extensionContext completeRequestReturningItems:nil completionHandler:nil]; 26 | [super dismissViewControllerAnimated:flag completion:completion]; 27 | } 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewControllerExample/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // TOCropViewControllerExample 4 | // 5 | // Created by Tim Oliver on 3/19/15. 6 | // Copyright (c) 2015 Tim Oliver. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | 17 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewControllerExample/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // TOCropViewControllerExample 4 | // 5 | // Created by Tim Oliver on 3/19/15. 6 | // Copyright (c) 2015 Tim Oliver. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | - (void)applicationWillResignActive:(UIApplication *)application { 24 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 25 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 26 | } 27 | 28 | - (void)applicationDidEnterBackground:(UIApplication *)application { 29 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | - (void)applicationWillEnterForeground:(UIApplication *)application { 34 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 35 | } 36 | 37 | - (void)applicationDidBecomeActive:(UIApplication *)application { 38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewControllerExample/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 22 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewControllerExample/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewControllerExample/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Objective-C/TOCropViewControllerExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSCameraUsageDescription 26 | $(PRODUCT_NAME) takes photos as sample images for cropping. 27 | NSPhotoLibraryUsageDescription 28 | $(PRODUCT_NAME) provides image editing services for images in the Photo Library. 29 | UILaunchStoryboardName 30 | LaunchScreen 31 | UIMainStoryboardFile 32 | Main 33 | UIRequiredDeviceCapabilities 34 | 35 | armv7 36 | 37 | UISupportedInterfaceOrientations 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationLandscapeLeft 41 | UIInterfaceOrientationLandscapeRight 42 | 43 | UISupportedInterfaceOrientations~ipad 44 | 45 | UIInterfaceOrientationPortrait 46 | UIInterfaceOrientationPortraitUpsideDown 47 | UIInterfaceOrientationLandscapeLeft 48 | UIInterfaceOrientationLandscapeRight 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewControllerExample/TOCropViewControllerExample-Extension.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewControllerExample/TOCropViewControllerExample.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.device.camera 8 | 9 | com.apple.security.network.client 10 | 11 | com.apple.security.personal-information.photos-library 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewControllerExample/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // TOCropViewControllerExample 4 | // 5 | // Created by Tim Oliver on 3/19/15. 6 | // Copyright (c) 2015 Tim Oliver. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | @end 14 | 15 | 16 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewControllerExample/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // TOCropViewControllerExample 4 | // 5 | // Created by Tim Oliver on 3/19/15. 6 | // Copyright (c) 2015 Tim Oliver. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "TOCropViewController.h" 11 | 12 | @interface ViewController () 13 | 14 | @property (nonatomic, strong) UIImage *image; // The image we'll be cropping 15 | @property (nonatomic, strong) UIImageView *imageView; // The image view to present the cropped image 16 | 17 | @property (nonatomic, assign) TOCropViewCroppingStyle croppingStyle; //The cropping style 18 | @property (nonatomic, assign) CGRect croppedFrame; 19 | @property (nonatomic, assign) NSInteger angle; 20 | 21 | @end 22 | 23 | @implementation ViewController 24 | 25 | #pragma mark - Image Picker Delegate - 26 | - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info 27 | { 28 | UIImage *image = info[UIImagePickerControllerOriginalImage]; 29 | TOCropViewController *cropController = [[TOCropViewController alloc] initWithCroppingStyle:self.croppingStyle image:image]; 30 | cropController.delegate = self; 31 | 32 | // Uncomment this if you wish to provide extra instructions via a title label 33 | //cropController.title = @"Crop Image"; 34 | 35 | // -- Uncomment these if you want to test out restoring to a previous crop setting -- 36 | //cropController.angle = 90; // The initial angle in which the image will be rotated 37 | //cropController.imageCropFrame = CGRectMake(0,0,2848,4288); //The initial frame that the crop controller will have visible. 38 | 39 | // -- Uncomment the following lines of code to test out the aspect ratio features -- 40 | //cropController.aspectRatioPreset = TOCropViewControllerAspectRatioPresetSquare; //Set the initial aspect ratio as a square 41 | //cropController.aspectRatioLockEnabled = YES; // The crop box is locked to the aspect ratio and can't be resized away from it 42 | //cropController.resetAspectRatioEnabled = NO; // When tapping 'reset', the aspect ratio will NOT be reset back to default 43 | //cropController.aspectRatioPickerButtonHidden = YES; 44 | 45 | // -- Uncomment this line of code to place the toolbar at the top of the view controller -- 46 | //cropController.toolbarPosition = TOCropViewControllerToolbarPositionTop; 47 | 48 | // -- Uncomment this line of code to include only certain type of preset ratios 49 | //cropController.allowedAspectRatios = @[@(TOCropViewControllerAspectRatioPresetOriginal), 50 | // @(TOCropViewControllerAspectRatioPresetSquare), 51 | // @(TOCropViewControllerAspectRatioPreset3x2)]; 52 | 53 | //cropController.rotateButtonsHidden = YES; 54 | //cropController.rotateClockwiseButtonHidden = NO; 55 | 56 | //cropController.reverseContentLayout = YES; 57 | 58 | //cropController.doneButtonTitle = @"Title"; 59 | //cropController.cancelButtonTitle = @"Title"; 60 | 61 | // -- Uncomment this line of code to show a confirmation dialog when cancelling -- 62 | //cropController.showCancelConfirmationDialog = YES; 63 | 64 | // Uncomment this if you wish to always show grid 65 | //cropController.cropView.alwaysShowCroppingGrid = YES; 66 | 67 | // Uncomment this if you do not want translucency effect 68 | //cropController.cropView.translucencyAlwaysHidden = YES; 69 | 70 | // Set toolbar action button colors 71 | // cropController.doneButtonColor = [UIColor redColor]; 72 | // cropController.cancelButtonColor = [UIColor greenColor]; 73 | 74 | self.image = image; 75 | 76 | //If profile picture, push onto the same navigation stack 77 | if (self.croppingStyle == TOCropViewCroppingStyleCircular) { 78 | #if TARGET_OS_IOS 79 | if (picker.sourceType == UIImagePickerControllerSourceTypeCamera) { 80 | [picker dismissViewControllerAnimated:YES completion:^{ 81 | [self presentViewController:cropController animated:YES completion:nil]; 82 | }]; 83 | } else { 84 | #endif 85 | [picker pushViewController:cropController animated:YES]; 86 | #if TARGET_OS_IOS 87 | } 88 | #endif 89 | } 90 | else { //otherwise dismiss, and then present from the main controller 91 | [picker dismissViewControllerAnimated:YES completion:^{ 92 | [self presentViewController:cropController animated:YES completion:nil]; 93 | //[self.navigationController pushViewController:cropController animated:YES]; 94 | }]; 95 | } 96 | } 97 | 98 | - (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker 99 | { 100 | [picker dismissViewControllerAnimated:YES completion:nil]; 101 | } 102 | 103 | #pragma mark - Gesture Recognizer - 104 | - (void)didTapImageView 105 | { 106 | // When tapping the image view, restore the image to the previous cropping state 107 | TOCropViewController *cropController = [[TOCropViewController alloc] initWithCroppingStyle:self.croppingStyle image:self.image]; 108 | cropController.delegate = self; 109 | CGRect viewFrame = [self.view convertRect:self.imageView.frame toView:self.navigationController.view]; 110 | [cropController presentAnimatedFromParentViewController:self 111 | fromImage:self.imageView.image 112 | fromView:nil 113 | fromFrame:viewFrame 114 | angle:self.angle 115 | toImageFrame:self.croppedFrame 116 | setup:^{ self.imageView.hidden = YES; } 117 | completion:nil]; 118 | } 119 | 120 | #pragma mark - Cropper Delegate - 121 | - (void)cropViewController:(TOCropViewController *)cropViewController didCropToImage:(UIImage *)image withRect:(CGRect)cropRect angle:(NSInteger)angle 122 | { 123 | self.croppedFrame = cropRect; 124 | self.angle = angle; 125 | [self updateImageViewWithImage:image fromCropViewController:cropViewController]; 126 | } 127 | 128 | - (void)cropViewController:(TOCropViewController *)cropViewController didCropToCircularImage:(UIImage *)image withRect:(CGRect)cropRect angle:(NSInteger)angle 129 | { 130 | self.croppedFrame = cropRect; 131 | self.angle = angle; 132 | [self updateImageViewWithImage:image fromCropViewController:cropViewController]; 133 | } 134 | 135 | - (void)updateImageViewWithImage:(UIImage *)image fromCropViewController:(TOCropViewController *)cropViewController 136 | { 137 | self.imageView.image = image; 138 | [self layoutImageView]; 139 | 140 | self.navigationItem.rightBarButtonItem.enabled = YES; 141 | 142 | if (cropViewController.croppingStyle != TOCropViewCroppingStyleCircular) { 143 | self.imageView.hidden = YES; 144 | [cropViewController dismissAnimatedFromParentViewController:self 145 | withCroppedImage:image 146 | toView:self.imageView 147 | toFrame:CGRectZero 148 | setup:^{ [self layoutImageView]; } 149 | completion: 150 | ^{ 151 | self.imageView.hidden = NO; 152 | }]; 153 | } 154 | else { 155 | self.imageView.hidden = NO; 156 | [cropViewController dismissViewControllerAnimated:YES completion:nil]; 157 | } 158 | } 159 | 160 | #pragma mark - Image Layout - 161 | - (void)layoutImageView 162 | { 163 | if (self.imageView.image == nil) 164 | return; 165 | 166 | CGFloat padding = 20.0f; 167 | 168 | CGRect viewFrame = self.view.bounds; 169 | viewFrame.size.width -= (padding * 2.0f); 170 | viewFrame.size.height -= ((padding * 2.0f)); 171 | 172 | CGRect imageFrame = CGRectZero; 173 | imageFrame.size = self.imageView.image.size; 174 | 175 | if (self.imageView.image.size.width > viewFrame.size.width || 176 | self.imageView.image.size.height > viewFrame.size.height) 177 | { 178 | CGFloat scale = MIN(viewFrame.size.width / imageFrame.size.width, viewFrame.size.height / imageFrame.size.height); 179 | imageFrame.size.width *= scale; 180 | imageFrame.size.height *= scale; 181 | imageFrame.origin.x = (CGRectGetWidth(self.view.bounds) - imageFrame.size.width) * 0.5f; 182 | imageFrame.origin.y = (CGRectGetHeight(self.view.bounds) - imageFrame.size.height) * 0.5f; 183 | self.imageView.frame = imageFrame; 184 | } 185 | else { 186 | self.imageView.frame = imageFrame; 187 | self.imageView.center = (CGPoint){CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds)}; 188 | } 189 | } 190 | 191 | #pragma mark - Bar Button Items - 192 | - (void)showCropViewController 193 | { 194 | UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet]; 195 | UIAlertAction *defaultAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Crop Image", @"") 196 | style:UIAlertActionStyleDefault 197 | handler:^(UIAlertAction *action) { 198 | self.croppingStyle = TOCropViewCroppingStyleDefault; 199 | 200 | UIImagePickerController *standardPicker = [[UIImagePickerController alloc] init]; 201 | standardPicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; 202 | standardPicker.allowsEditing = NO; 203 | standardPicker.delegate = self; 204 | [self presentViewController:standardPicker animated:YES completion:nil]; 205 | }]; 206 | 207 | UIAlertAction *profileAction = [UIAlertAction actionWithTitle:NSLocalizedString(@"Make Profile Picture", @"") 208 | style:UIAlertActionStyleDefault 209 | handler:^(UIAlertAction *action) { 210 | self.croppingStyle = TOCropViewCroppingStyleCircular; 211 | 212 | UIImagePickerController *profilePicker = [[UIImagePickerController alloc] init]; 213 | profilePicker.modalPresentationStyle = UIModalPresentationPopover; 214 | profilePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; 215 | profilePicker.allowsEditing = NO; 216 | profilePicker.delegate = self; 217 | profilePicker.preferredContentSize = CGSizeMake(512,512); 218 | profilePicker.popoverPresentationController.barButtonItem = self.navigationItem.leftBarButtonItem; 219 | [self presentViewController:profilePicker animated:YES completion:nil]; 220 | }]; 221 | 222 | [alertController addAction:defaultAction]; 223 | [alertController addAction:profileAction]; 224 | [alertController setModalPresentationStyle:UIModalPresentationPopover]; 225 | 226 | UIPopoverPresentationController *popPresenter = [alertController popoverPresentationController]; 227 | popPresenter.barButtonItem = self.navigationItem.leftBarButtonItem; 228 | [self presentViewController:alertController animated:YES completion:nil]; 229 | } 230 | 231 | - (void)sharePhoto:(id)sender 232 | { 233 | if (self.imageView.image == nil) 234 | return; 235 | 236 | UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:@[self.imageView.image] applicationActivities:nil]; 237 | activityController.modalPresentationStyle = UIModalPresentationPopover; 238 | activityController.popoverPresentationController.barButtonItem = sender; 239 | [self presentViewController:activityController animated:YES completion:nil]; 240 | } 241 | 242 | - (void)dismissViewController { 243 | [self dismissViewControllerAnimated:YES completion:nil]; 244 | } 245 | 246 | #pragma mark - View Creation/Lifecycle - 247 | - (void)viewDidLoad { 248 | [super viewDidLoad]; 249 | self.title = NSLocalizedString(@"TOCropViewController", @""); 250 | 251 | self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(showCropViewController)]; 252 | 253 | #if TARGET_APP_EXTENSION 254 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:@selector(dismissViewController)]; 255 | #else 256 | self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAction target:self action:@selector(sharePhoto:)]; 257 | self.navigationItem.rightBarButtonItem.enabled = NO; 258 | #endif 259 | 260 | self.imageView = [[UIImageView alloc] init]; 261 | self.imageView.userInteractionEnabled = YES; 262 | self.imageView.contentMode = UIViewContentModeScaleAspectFit; 263 | [self.view addSubview:self.imageView]; 264 | 265 | if (@available(iOS 11.0, *)) { 266 | self.imageView.accessibilityIgnoresInvertColors = YES; 267 | } 268 | 269 | UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(didTapImageView)]; 270 | [self.imageView addGestureRecognizer:tapRecognizer]; 271 | } 272 | 273 | - (void)viewDidLayoutSubviews 274 | { 275 | [super viewDidLayoutSubviews]; 276 | [self layoutImageView]; 277 | } 278 | 279 | @end 280 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewControllerExample/ar.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = " Copyright (c) 2015 Tim Oliver. All rights reserved."; ObjectID = "8ie-xW-0ye"; */ 3 | "8ie-xW-0ye.text" = " Copyright (c) 2015 Tim Oliver. All rights reserved."; 4 | 5 | /* Class = "UILabel"; text = "TOCropViewControllerExample"; ObjectID = "kId-c2-rCX"; */ 6 | "kId-c2-rCX.text" = "TOCropViewControllerExample"; 7 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewControllerExample/ar.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = "Tap '+' to choose a photo."; ObjectID = "SiC-JY-tpF"; */ 3 | "SiC-JY-tpF.text" = "Tap '+' to choose a photo."; 4 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewControllerExample/fr.lproj/LaunchScreen.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = " Copyright (c) 2015 Tim Oliver. All rights reserved."; ObjectID = "8ie-xW-0ye"; */ 3 | "8ie-xW-0ye.text" = " Copyright (c) 2015 Tim Oliver. All rights reserved."; 4 | 5 | /* Class = "UILabel"; text = "TOCropViewControllerExample"; ObjectID = "kId-c2-rCX"; */ 6 | "kId-c2-rCX.text" = "TOCropViewControllerExample"; 7 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewControllerExample/fr.lproj/Main.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TimOliver/TOCropViewController/a634cb7cdfd580006e79a6e74e64417fe9e9783b/Objective-C/TOCropViewControllerExample/fr.lproj/Main.strings -------------------------------------------------------------------------------- /Objective-C/TOCropViewControllerExample/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // TOCropViewControllerExample 4 | // 5 | // Created by Tim Oliver on 3/19/15. 6 | // Copyright (c) 2015 Tim Oliver. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewControllerExample/tr.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "UILabel"; text = "Tap '+' to choose a photo."; ObjectID = "SiC-JY-tpF"; */ 3 | "SiC-JY-tpF.text" = "Fotoğraf seçmek için +'ya basın."; 4 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewControllerTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /Objective-C/TOCropViewControllerTests/TOCropViewControllerTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // TOCropViewControllerTests.m 3 | // TOCropViewControllerTests 4 | // 5 | // Created by Tim Oliver on 14/06/2015. 6 | // Copyright (c) 2015 Tim Oliver. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | #import "TOCropViewController.h" 13 | 14 | @interface TOCropViewControllerTests : XCTestCase 15 | 16 | @end 17 | 18 | @implementation TOCropViewControllerTests 19 | 20 | - (void)testViewControllerInstance { 21 | //Create a basic image 22 | UIGraphicsBeginImageContextWithOptions((CGSize){10, 10}, NO, 1.0f); 23 | CGContextFillRect(UIGraphicsGetCurrentContext(), (CGRect){0,0,10,10}); 24 | UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); 25 | UIGraphicsEndImageContext(); 26 | 27 | //Perform test 28 | TOCropViewController *controller = [[TOCropViewController alloc] initWithImage:image]; 29 | UIView *view = controller.view; 30 | XCTAssertNotNil(view); 31 | } 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.9 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "TOCropViewController", 7 | defaultLocalization: "en", 8 | platforms: [.iOS(.v11)], 9 | products: [ 10 | .library( 11 | name: "TOCropViewController", 12 | targets: ["TOCropViewController"] 13 | ), 14 | .library( 15 | name: "CropViewController", 16 | targets: ["CropViewController"] 17 | ) 18 | ], 19 | targets: [ 20 | .target( 21 | name: "TOCropViewController", 22 | path: "Objective-C/TOCropViewController/", 23 | exclude:["Supporting/Info.plist"], 24 | resources: [.process("Resources")], 25 | publicHeadersPath: "include" 26 | ), 27 | .target( 28 | name: "CropViewController", 29 | dependencies: ["TOCropViewController"], 30 | path: "Swift/CropViewController/", 31 | exclude:["Info.plist"], 32 | sources: ["CropViewController.swift"] 33 | ) 34 | ] 35 | ) 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TOCropViewController 2 | 3 |

4 | 5 |

6 | 7 | [![CI](https://github.com/TimOliver/TOCropViewController/workflows/CI/badge.svg)](https://github.com/TimOliver/TOCropViewController/actions?query=workflow%3ACI) 8 | ![Version](https://img.shields.io/cocoapods/v/TOCropViewController.svg?style=flat) 9 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 10 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/TimOliver/TOCropViewController/master/LICENSE) 11 | ![Platform](https://img.shields.io/cocoapods/p/TOCropViewController.svg?style=flat) 12 | 13 | `TOCropViewController` is an open-source `UIViewController` subclass to crop out sections of `UIImage` objects, as well as perform basic rotations. It is excellent for things like editing profile pictures, or sharing parts of a photo online. It has been designed with the iOS Photos app editor in mind, and as such, behaves in a way that should already feel familiar to users of iOS. 14 | 15 | For Swift developers, `CropViewController` is a Swift wrapper that completely encapsulates `TOCropViewController` and provides a much more native, Swiftier interface. 16 | 17 | #### Proudly powering apps by 18 | 19 |

20 | 21 |

22 | 23 | _Looking for something more? If `TOCropViewController` doesn't meet your exact requirements, please consider [IMG.LY](https://img.ly/?via=tim) with video editing and photo filter capabilities instead! (Disclaimer: Affiliate Link)_ 24 | 25 | ## Features 26 | * Crop images by dragging the edges of a grid overlay. 27 | * Optionally, crop circular copies of images. 28 | * Rotate images in 90-degree segments. 29 | * Clamp the crop box to a specific aspect ratio. 30 | * A reset button to completely undo all changes. 31 | * iOS 7/8 translucency to make it easier to view the cropped region. 32 | * The choice of having the controller return the cropped image to a delegate, or immediately pass it to a `UIActivityViewController`. 33 | * A custom animation and layout when the device is rotated to landscape mode. 34 | * Custom 'opening' and 'dismissal' animations. 35 | * Localized in 28 languages. 36 | 37 | ## System Requirements 38 | iOS 11.0 or above 39 | 40 | ## Installation 41 | 42 |
43 | CocoaPods 44 | 45 |

Objective-C

46 | 47 | Add the following to your Podfile: 48 | ``` ruby 49 | pod 'TOCropViewController' 50 | ``` 51 | 52 |

Swift

53 | 54 | Add the following to your Podfile: 55 | ``` ruby 56 | pod 'CropViewController' 57 | ``` 58 |
59 | 60 |
61 | Swift Package Manager 62 | 63 | Add the following to your `Package.swift`: 64 | ``` swift 65 | dependencies: [ 66 | // ... 67 | .package(url: "https://github.com/TimOliver/TOCropViewController.git"), 68 | ], 69 | ``` 70 |
71 | 72 |
73 | Carthage 74 | 75 | 1. Add the following to your Cartfile: 76 | ``` 77 | github "TimOliver/TOCropViewController" 78 | ``` 79 | 80 | 2. Run `carthage update` 81 | 82 | 3. From the `Carthage/Build` folder, import one of the two frameworks into your Xcode project. For Objective-C projects, import just `TOCropViewController.framework` and for Swift, import `CropViewController.framework` instead. Each framework is separate; you do not need to import both. 83 | 84 | 4. Follow the remaining steps on [Getting Started with Carthage](https://github.com/Carthage/Carthage#getting-started) to finish integrating the framework. 85 | 86 |
87 | 88 |
89 | Manual Installation 90 | 91 | All of the necessary source and resource files for `TOCropViewController` are in `Objective-C/TOCropViewController`, and all of the necessary Swift files are in `Swift/CropViewController`. 92 | 93 | For Objective-C projects, copy just the `TOCropViewController` directory to your Xcode project. For Swift projects, copy both `TOCropViewController` and `CropViewController` to your project. 94 |
95 | 96 | ## Examples 97 | Using `TOCropViewController` is very straightforward. Simply create a new instance passing the `UIImage` object you wish to crop, and then present it modally on the screen. 98 | 99 | While `TOCropViewController` prefers to be presented modally, it can also be pushed to a `UINavigationController` stack. 100 | 101 | For a complete working example, check out the sample apps included in this repo. 102 | 103 |
104 | Basic Implementation 105 | 106 | #### Swift 107 | ```swift 108 | func presentCropViewController() { 109 | let image: UIImage = ... //Load an image 110 | 111 | let cropViewController = CropViewController(image: image) 112 | cropViewController.delegate = self 113 | present(cropViewController, animated: true, completion: nil) 114 | } 115 | 116 | func cropViewController(_ cropViewController: CropViewController, didCropToImage image: UIImage, withRect cropRect: CGRect, angle: Int) { 117 | // 'image' is the newly cropped version of the original image 118 | } 119 | ``` 120 | 121 | #### Objective-C 122 | ```objc 123 | - (void)presentCropViewController 124 | { 125 | UIImage *image = ...; // Load an image 126 | 127 | TOCropViewController *cropViewController = [[TOCropViewController alloc] initWithImage:image]; 128 | cropViewController.delegate = self; 129 | [self presentViewController:cropViewController animated:YES completion:nil]; 130 | } 131 | 132 | - (void)cropViewController:(TOCropViewController *)cropViewController didCropToImage:(UIImage *)image withRect:(CGRect)cropRect angle:(NSInteger)angle 133 | { 134 | // 'image' is the newly cropped version of the original image 135 | } 136 | ``` 137 | 138 | Similar to many `UIKit` `UIViewController` subclasses, like `MFMailComposeViewController`, the class responsible for presenting view controller should also take care of dismissing it upon cancellation. To dismiss `TOCropViewController`, implement the `cropViewController:didFinishCancelled:` delegate method, and call `dismissViewController:animated:` from there. 139 |
140 | 141 |
142 | Making a Circular Cropped Image 143 | 144 | #### Swift 145 | ```swift 146 | func presentCropViewController() { 147 | var image: UIImage? // Load an image 148 | let cropViewController = CropViewController(croppingStyle: .circular, image: image) 149 | cropViewController.delegate = self 150 | self.present(cropViewController, animated: true, completion: nil) 151 | } 152 | 153 | func cropViewController(_ cropViewController: TOCropViewController?, didCropToCircularImage image: UIImage?, with cropRect: CGRect, angle: Int) { 154 | // 'image' is the newly cropped, circular version of the original image 155 | } 156 | ``` 157 | 158 | 159 | #### Objective-C 160 | ```objc 161 | - (void)presentCropViewController 162 | { 163 | UIImage *image = ...; // Load an image 164 | 165 | TOCropViewController *cropViewController = [[TOCropViewController alloc] initWithCroppingStyle:TOCropViewCroppingStyleCircular image:image]; 166 | cropViewController.delegate = self; 167 | [self presentViewController:cropViewController animated:YES completion:nil]; 168 | } 169 | 170 | - (void)cropViewController:(TOCropViewController *)cropViewController didCropToCircularImage:(UIImage *)image withRect:(CGRect)cropRect angle:(NSInteger)angle 171 | { 172 | // 'image' is the newly cropped, circular version of the original image 173 | } 174 | ``` 175 |
176 | 177 |
178 | Sharing Cropped Images Via a Share Sheet 179 | 180 | #### Swift 181 | ```swift 182 | func presentCropViewController() { 183 | var image: UIImage? // Load an image 184 | let cropViewController = CropViewController(image: image) 185 | cropViewController.showActivitySheetOnDone = true 186 | self.present(cropViewController, animated: true, completion: nil) 187 | } 188 | ``` 189 | 190 | #### Objective-C 191 | ```objc 192 | - (void)presentCropViewController 193 | { 194 | UIImage *image = ...; // Load an image 195 | 196 | TOCropViewController *cropViewController = [[TOCropViewController alloc] initWithImage:image]; 197 | cropViewController.showActivitySheetOnDone = YES; 198 | [self presentViewController:cropViewController animated:YES completion:nil]; 199 | } 200 | ``` 201 |
202 | 203 |
204 | Presenting With a Custom Animation 205 | 206 | Optionally, `TOCropViewController` also supports a custom presentation animation where an already-visible copy of the image will zoom in to fill the screen. 207 | 208 | #### Swift 209 | ```swift 210 | 211 | func presentCropViewController() { 212 | var image: UIImage? // Load an image 213 | var imageView = UIImageView(image: image) 214 | var frame: CGRect = view.convert(imageView.frame, to: view) 215 | 216 | let cropViewController = CropViewController(image: image) 217 | cropViewController.delegate = self 218 | self.present(cropViewController, animated: true, completion: nil) 219 | cropViewController.presentAnimated(fromParentViewController: self, fromFrame: frame, completion: nil) 220 | } 221 | ``` 222 | 223 | #### Objective-C 224 | ```objc 225 | - (void)presentCropViewController 226 | { 227 | UIImage *image = ...; 228 | UIImageView *imageView = [[UIImageView alloc] initWithImage:image]; 229 | CGRect frame = [self.view convertRect:imageView.frame toView:self.view]; 230 | 231 | TOCropViewController *cropViewController = [[TOCropViewController alloc] initWithImage:image]; 232 | cropViewController.delegate = self; 233 | [self presentViewController:cropViewController animated:YES completion:nil]; 234 | [cropViewController presentAnimatedFromParentViewController:self fromFrame:frame completion:nil]; 235 | } 236 | ``` 237 |
238 | 239 | ## Architecture of `TOCropViewController` 240 | While traditional cropping UI implementations will usually just have a dimming view with a square hole cut out of the middle, `TOCropViewController` goes about its implementation a little differently. 241 | 242 |

243 | 244 |

245 | 246 | Since there are two views that are overlaid over the image (A dimming view and a translucency view), trying to cut a hole open in both of them would be rather complex. Instead, an image view is placed in a scroll view in the background, and a copy of the image view is placed on top, inside a container view that is clipped to the designated cropping size. The size and position of the foreground image is then made to match the background view, creating the illusion that there is a hole in the dimming views, and minimising the number of views onscreen. 247 | 248 | ## Credits 249 | `TOCropViewController` was originally created by [Tim Oliver](http://twitter.com/TimOliverAU) as a component for [iComics](http://icomics.co), a comic reader app for iOS. 250 | 251 | Thanks also goes to `TOCropViewController`'s growing list of [contributors](https://github.com/TimOliver/TOCropViewController/graphs/contributors)! 252 | 253 | iOS Device mockups used in the screenshot created by [Pixeden](http://www.pixeden.com). 254 | 255 | ## License 256 | TOCropViewController is licensed under the MIT License, please see the [LICENSE](LICENSE) file. 257 | -------------------------------------------------------------------------------- /Swift/CropViewController/CropViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // CropViewController.h 3 | // 4 | // Copyright 2017-2024 Timothy Oliver. All rights reserved. 5 | // 6 | // Permission is hereby granted, free of charge, to any person obtaining a copy 7 | // of this software and associated documentation files (the "Software"), to 8 | // deal in the Software without restriction, including without limitation the 9 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | // sell copies of the Software, and to permit persons to whom the Software is 11 | // furnished to do so, subject to the following conditions: 12 | // 13 | // The above copyright notice and this permission notice shall be included in 14 | // all copies or substantial portions of the Software. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 21 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | #import 24 | 25 | #import "TOCropViewController.h" 26 | #import "TOCropView.h" 27 | #import "TOCropToolbar.h" 28 | #import "TOCropViewConstants.h" 29 | #import "UIImage+CropRotate.h" 30 | 31 | FOUNDATION_EXPORT double CropViewControllerVersionNumber; 32 | FOUNDATION_EXPORT const unsigned char CropViewControllerVersionString[]; 33 | 34 | -------------------------------------------------------------------------------- /Swift/CropViewController/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Swift/CropViewControllerExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // CropViewControllerExample 4 | // 5 | // Created by Tim Oliver on 18/11/17. 6 | // Copyright © 2017 Tim Oliver. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /Swift/CropViewControllerExample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Swift/CropViewControllerExample/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 31 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /Swift/CropViewControllerExample/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /Swift/CropViewControllerExample/CropViewController-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // CropViewController-Bridging-Header.h 3 | // CropViewControllerExample 4 | // 5 | // Created by Tim Oliver on 23/7/18. 6 | // Copyright © 2018 Tim Oliver. All rights reserved. 7 | // 8 | 9 | #import "TOCropViewController.h" 10 | -------------------------------------------------------------------------------- /Swift/CropViewControllerExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSPhotoLibraryUsageDescription 24 | Crop and rotate your photos! 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Swift/CropViewControllerExample/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // CropViewControllerExample 4 | // 5 | // Created by Tim Oliver on 18/11/17. 6 | // Copyright © 2017 Tim Oliver. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController, CropViewControllerDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate { 12 | 13 | private let imageView = UIImageView() 14 | 15 | private var image: UIImage? 16 | private var croppingStyle = CropViewCroppingStyle.default 17 | 18 | private var croppedRect = CGRect.zero 19 | private var croppedAngle = 0 20 | 21 | func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { 22 | guard let image = (info[UIImagePickerController.InfoKey.originalImage] as? UIImage) else { return } 23 | 24 | let cropController = CropViewController(croppingStyle: croppingStyle, image: image) 25 | //cropController.modalPresentationStyle = .fullScreen 26 | cropController.delegate = self 27 | 28 | // Uncomment this if you wish to provide extra instructions via a title label 29 | //cropController.title = "Crop Image" 30 | 31 | // -- Uncomment these if you want to test out restoring to a previous crop setting -- 32 | //cropController.angle = 90 // The initial angle in which the image will be rotated 33 | //cropController.imageCropFrame = CGRect(x: 0, y: 0, width: 2848, height: 4288) //The initial frame that the crop controller will have visible. 34 | 35 | // -- Uncomment the following lines of code to test out the aspect ratio features -- 36 | //cropController.aspectRatioPreset = .presetSquare; //Set the initial aspect ratio as a square 37 | //cropController.aspectRatioLockEnabled = true // The crop box is locked to the aspect ratio and can't be resized away from it 38 | //cropController.resetAspectRatioEnabled = false // When tapping 'reset', the aspect ratio will NOT be reset back to default 39 | //cropController.aspectRatioPickerButtonHidden = true 40 | 41 | // -- Uncomment this line of code to place the toolbar at the top of the view controller -- 42 | //cropController.toolbarPosition = .top 43 | 44 | //cropController.rotateButtonsHidden = true 45 | //cropController.rotateClockwiseButtonHidden = true 46 | 47 | //cropController.doneButtonTitle = "Title" 48 | //cropController.cancelButtonTitle = "Title" 49 | 50 | //cropController.toolbar.doneButtonHidden = true 51 | //cropController.toolbar.cancelButtonHidden = true 52 | //cropController.toolbar.clampButtonHidden = true 53 | 54 | // Set toolbar action button colors 55 | // cropController.doneButtonColor = UIColor.red 56 | // cropController.cancelButtonColor = UIColor.green 57 | 58 | // Change toolbar layout direction 59 | // cropController.toolbar.reverseContentLayout = true 60 | 61 | self.image = image 62 | 63 | //If profile picture, push onto the same navigation stack 64 | if croppingStyle == .circular { 65 | if picker.sourceType == .camera { 66 | picker.dismiss(animated: true, completion: { 67 | self.present(cropController, animated: true, completion: nil) 68 | }) 69 | } else { 70 | picker.pushViewController(cropController, animated: true) 71 | } 72 | } 73 | else { //otherwise dismiss, and then present from the main controller 74 | picker.dismiss(animated: true, completion: { 75 | self.present(cropController, animated: true, completion: nil) 76 | //self.navigationController!.pushViewController(cropController, animated: true) 77 | }) 78 | } 79 | } 80 | 81 | public func cropViewController(_ cropViewController: CropViewController, didCropToImage image: UIImage, withRect cropRect: CGRect, angle: Int) { 82 | self.croppedRect = cropRect 83 | self.croppedAngle = angle 84 | updateImageViewWithImage(image, fromCropViewController: cropViewController) 85 | } 86 | 87 | public func cropViewController(_ cropViewController: CropViewController, didCropToCircularImage image: UIImage, withRect cropRect: CGRect, angle: Int) { 88 | self.croppedRect = cropRect 89 | self.croppedAngle = angle 90 | updateImageViewWithImage(image, fromCropViewController: cropViewController) 91 | } 92 | 93 | public func updateImageViewWithImage(_ image: UIImage, fromCropViewController cropViewController: CropViewController) { 94 | imageView.image = image 95 | layoutImageView() 96 | 97 | self.navigationItem.leftBarButtonItem?.isEnabled = true 98 | 99 | if cropViewController.croppingStyle != .circular { 100 | imageView.isHidden = true 101 | 102 | cropViewController.dismissAnimatedFrom(self, withCroppedImage: image, 103 | toView: imageView, 104 | toFrame: CGRect.zero, 105 | setup: { self.layoutImageView() }, 106 | completion: { 107 | self.imageView.isHidden = false }) 108 | } 109 | else { 110 | self.imageView.isHidden = false 111 | cropViewController.dismiss(animated: true, completion: nil) 112 | } 113 | } 114 | 115 | override func viewDidLoad() { 116 | super.viewDidLoad() 117 | 118 | title = NSLocalizedString("CropViewController", comment: "") 119 | 120 | navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addButtonTapped(sender:))) 121 | navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(sharePhoto)) 122 | navigationItem.leftBarButtonItem?.isEnabled = false 123 | 124 | imageView.isUserInteractionEnabled = true 125 | imageView.contentMode = .scaleAspectFit 126 | if #available(iOS 11.0, *) { 127 | imageView.accessibilityIgnoresInvertColors = true 128 | } 129 | view.addSubview(imageView) 130 | 131 | let tapRecognizer = UITapGestureRecognizer.init(target: self, action: #selector(didTapImageView)) 132 | imageView.addGestureRecognizer(tapRecognizer) 133 | } 134 | 135 | override func didReceiveMemoryWarning() { 136 | super.didReceiveMemoryWarning() 137 | // Dispose of any resources that can be recreated. 138 | } 139 | 140 | @objc public func addButtonTapped(sender: AnyObject) { 141 | let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) 142 | let defaultAction = UIAlertAction(title: "Crop Image", style: .default) { (action) in 143 | self.croppingStyle = .default 144 | 145 | let imagePicker = UIImagePickerController() 146 | imagePicker.sourceType = .photoLibrary 147 | imagePicker.allowsEditing = false 148 | imagePicker.delegate = self 149 | self.present(imagePicker, animated: true, completion: nil) 150 | } 151 | 152 | let profileAction = UIAlertAction(title: "Make Profile Picture", style: .default) { (action) in 153 | self.croppingStyle = .circular 154 | 155 | let imagePicker = UIImagePickerController() 156 | imagePicker.modalPresentationStyle = .popover 157 | imagePicker.popoverPresentationController?.barButtonItem = (sender as! UIBarButtonItem) 158 | imagePicker.preferredContentSize = CGSize(width: 320, height: 568) 159 | imagePicker.sourceType = .photoLibrary 160 | imagePicker.allowsEditing = false 161 | imagePicker.delegate = self 162 | self.present(imagePicker, animated: true, completion: nil) 163 | } 164 | 165 | alertController.addAction(defaultAction) 166 | alertController.addAction(profileAction) 167 | alertController.modalPresentationStyle = .popover 168 | 169 | let presentationController = alertController.popoverPresentationController 170 | presentationController?.barButtonItem = (sender as! UIBarButtonItem) 171 | present(alertController, animated: true, completion: nil) 172 | } 173 | 174 | @objc public func didTapImageView() { 175 | // When tapping the image view, restore the image to the previous cropping state 176 | let cropViewController = CropViewController(croppingStyle: self.croppingStyle, image: self.image!) 177 | cropViewController.delegate = self 178 | let viewFrame = view.convert(imageView.frame, to: navigationController!.view) 179 | 180 | cropViewController.presentAnimatedFrom(self, 181 | fromImage: self.imageView.image, 182 | fromView: nil, 183 | fromFrame: viewFrame, 184 | angle: self.croppedAngle, 185 | toImageFrame: self.croppedRect, 186 | setup: { self.imageView.isHidden = true }, 187 | completion: nil) 188 | } 189 | 190 | public override func viewDidLayoutSubviews() { 191 | super.viewDidLayoutSubviews() 192 | layoutImageView() 193 | } 194 | 195 | public func layoutImageView() { 196 | guard imageView.image != nil else { return } 197 | 198 | let padding: CGFloat = 20.0 199 | 200 | var viewFrame = self.view.bounds 201 | viewFrame.size.width -= (padding * 2.0) 202 | viewFrame.size.height -= ((padding * 2.0)) 203 | 204 | var imageFrame = CGRect.zero 205 | imageFrame.size = imageView.image!.size; 206 | 207 | if imageView.image!.size.width > viewFrame.size.width || imageView.image!.size.height > viewFrame.size.height { 208 | let scale = min(viewFrame.size.width / imageFrame.size.width, viewFrame.size.height / imageFrame.size.height) 209 | imageFrame.size.width *= scale 210 | imageFrame.size.height *= scale 211 | imageFrame.origin.x = (self.view.bounds.size.width - imageFrame.size.width) * 0.5 212 | imageFrame.origin.y = (self.view.bounds.size.height - imageFrame.size.height) * 0.5 213 | imageView.frame = imageFrame 214 | } 215 | else { 216 | self.imageView.frame = imageFrame; 217 | self.imageView.center = CGPoint(x: self.view.bounds.midX, y: self.view.bounds.midY) 218 | } 219 | } 220 | 221 | @objc public func sharePhoto() { 222 | guard let image = imageView.image else { 223 | return 224 | } 225 | 226 | let activityController = UIActivityViewController(activityItems: [image], applicationActivities: nil) 227 | activityController.popoverPresentationController?.barButtonItem = navigationItem.rightBarButtonItem! 228 | present(activityController, animated: true, completion: nil) 229 | } 230 | } 231 | 232 | -------------------------------------------------------------------------------- /TOCropViewController.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = 'TOCropViewController' 3 | s.version = '2.7.4' 4 | s.license = { :type => 'MIT', :file => 'LICENSE' } 5 | s.summary = 'A view controller that enables cropping and rotation of UIImage objects.' 6 | s.homepage = 'https://github.com/TimOliver/TOCropViewController' 7 | s.author = 'Tim Oliver' 8 | s.source = { :git => 'https://github.com/TimOliver/TOCropViewController.git', :tag => s.version } 9 | s.platform = :ios, '11.0' 10 | s.source_files = 'Objective-C/TOCropViewController/**/*.{h,m}' 11 | s.exclude_files = 'Objective-C/TOCropViewController/include/**/*.h' 12 | s.resource_bundles = { 13 | 'TOCropViewControllerBundle' => ['Objective-C/TOCropViewController/**/*.{lproj,xcprivacy}'] 14 | } 15 | s.requires_arc = true 16 | end 17 | -------------------------------------------------------------------------------- /TOCropViewControllerExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TOCropViewControllerExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TOCropViewControllerExample.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TOCropViewControllerExample.xcodeproj/xcshareddata/xcschemes/CropViewController.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 52 | 53 | 59 | 60 | 66 | 67 | 68 | 69 | 71 | 72 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /TOCropViewControllerExample.xcodeproj/xcshareddata/xcschemes/CropViewControllerExample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 54 | 56 | 62 | 63 | 64 | 65 | 71 | 73 | 79 | 80 | 81 | 82 | 84 | 85 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /TOCropViewControllerExample.xcodeproj/xcshareddata/xcschemes/TOCropViewController.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 52 | 53 | 59 | 60 | 66 | 67 | 68 | 69 | 71 | 72 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /TOCropViewControllerExample.xcodeproj/xcshareddata/xcschemes/TOCropViewControllerExample-Extension.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 6 | 9 | 10 | 16 | 22 | 23 | 24 | 30 | 36 | 37 | 38 | 39 | 40 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 69 | 71 | 77 | 78 | 79 | 80 | 88 | 90 | 96 | 97 | 98 | 99 | 101 | 102 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /TOCropViewControllerExample.xcodeproj/xcshareddata/xcschemes/TOCropViewControllerExample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 43 | 49 | 50 | 51 | 52 | 53 | 58 | 59 | 65 | 66 | 67 | 68 | 70 | 76 | 77 | 78 | 80 | 86 | 87 | 88 | 89 | 90 | 100 | 102 | 108 | 109 | 110 | 111 | 117 | 119 | 125 | 126 | 127 | 128 | 130 | 131 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /TOCropViewControllerExample.xcodeproj/xcshareddata/xcschemes/TOCropViewControllerTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 42 | 48 | 49 | 50 | 51 | 52 | 62 | 63 | 69 | 70 | 71 | 72 | 78 | 79 | 85 | 86 | 87 | 88 | 90 | 91 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /breakdown.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TimOliver/TOCropViewController/a634cb7cdfd580006e79a6e74e64417fe9e9783b/breakdown.jpg -------------------------------------------------------------------------------- /buildkite/pipeline.release.yml: -------------------------------------------------------------------------------- 1 | env: 2 | LC_ALL: "en_US.UTF-8" 3 | REPO_PATH: "TimOliver/TOCropViewController" 4 | PODSPEC_PATHS: "TOCropViewController.podspec,CropViewController.podspec" 5 | FRAMEWORK_PLIST_PATHS: "Objective-C/TOCropViewController/Supporting/Info.plist,Swift/CropViewController/Info.plist" 6 | BUILDKITE_CLEAN_CHECKOUT: true 7 | 8 | steps: 9 | - label: ':fastlane: Cut New Release' 10 | command: 'bundle update && bundle exec fastlane release' 11 | -------------------------------------------------------------------------------- /buildkite/pipeline.test.yml: -------------------------------------------------------------------------------- 1 | env: 2 | TEST_SCHEME: "TOCropViewControllerTests" 3 | 4 | steps: 5 | - label: ':fastlane: Run Tests' 6 | command: 'bundle install && bundle exec fastlane test' 7 | -------------------------------------------------------------------------------- /fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | default_platform(:ios) 2 | 3 | desc "Runs the unit tests to ensure the build is working" 4 | lane :test do 5 | scan(scheme: ENV["TEST_SCHEME"], devices: ["iPhone 14"], clean: true) 6 | end 7 | 8 | desc "Cuts a new release and distributes it on CocoaPods and Carthage" 9 | lane :release do 10 | 11 | UI.message "Fetching latest tag" 12 | 13 | # Fetch the latest tag that would have just been added 14 | 15 | # We get the desired tag number from the Buildkite release message 16 | release_message = ENV["BUILDKITE_MESSAGE"] 17 | 18 | # See if we can extract the new version from the message 19 | latest_version = release_message.scan(/[vV]?(\d{1,3}\.\d{1,3}\.\d{1,3})/).last.first 20 | if !latest_version 21 | UI.user_error! "--- ERROR: Was unable to find version number in build message. ---" 22 | next 23 | end 24 | 25 | UI.success "--- Found latest tag version: #{latest_version} ---" 26 | 27 | UI.message "Extracting Release Notes from CHANGELOG" 28 | 29 | # Load the Release Notes from file, and throw an error if they weren't updated 30 | changelog_contents = File.read("../CHANGELOG.md") 31 | v = latest_version.split(".") 32 | release_notes = changelog_contents.scan(/#{v[0]}\.#{v[1]}\.#{v[2]}\ [Rr]elease\ [Nn]otes.*\n\=+\n([\s\S]*?)(\d{1,3}\.\d{1,3}\.\d{1,3}\ [Rr]elease\ [Nn]otes.*\n\=+\n|\Z)/).last 33 | if !release_notes 34 | UI.user_error! "--- ERROR: Unable to find Release Notes entry for v#{latest_version} in CHANGELOG. ---" 35 | next 36 | end 37 | 38 | UI.message "Release notes for v#{latest_version} found!" 39 | 40 | UI.message "Bumping Podspec Version" 41 | 42 | # Get the current commit sha to keep as backup in case the next commits have already previously finished 43 | commit_sha = last_git_commit[:commit_hash] 44 | 45 | # Update the Podspec version and push to the repo 46 | ENV["PODSPEC_PATHS"].split(",").each do |podspec| 47 | podspec_version = version_get_podspec(path: "./#{podspec}") 48 | if podspec_version != latest_version 49 | UI.message "Podspec version was #{podspec_version}. Updating Podspec version" 50 | version_bump_podspec(path: "./#{podspec}", version_number: latest_version) 51 | commit = commit_file(file_path: "#{podspec}", commit_message: "Bumped Podspec Version to v#{latest_version} [skip ci]") 52 | commit_sha = commit[:json]["commit"]["sha"] 53 | end 54 | end 55 | 56 | UI.message "Podspec version bumped!" 57 | 58 | UI.message "Bumping framework version" 59 | 60 | # Update and push framework version number if the current version doesn't match 61 | ENV["FRAMEWORK_PLIST_PATHS"].split(",").each do |framework_path| 62 | framework_version = get_info_plist_value(path: "./#{framework_path}", key: "CFBundleShortVersionString") 63 | if framework_version != latest_version 64 | UI.message "Framework version was #{framework_version}. Updating framework version" 65 | set_info_plist_value(path: "./#{framework_path}", key: "CFBundleShortVersionString", value: latest_version) 66 | commit = commit_file(file_path: "#{framework_path}", commit_message: "Bumped Framework Version to v#{latest_version} [skip ci]") 67 | commit_sha = commit[:json]["commit"]["sha"] 68 | end 69 | end 70 | 71 | UI.message "Framework version bumped!" 72 | 73 | UI.message "Pushing new tag to GitHub" 74 | 75 | # Create a new tag and commit it against the last commit 76 | push_new_tag(tag: latest_version, commit_sha: commit_sha) 77 | 78 | UI.message "Uploading to GitHub Releases" 79 | 80 | # Publish the Release on GitHub 81 | set_github_release(repository_name: ENV["REPO_PATH"], 82 | api_token: ENV["GITHUB_TOKEN"], 83 | name: "v#{latest_version}", 84 | tag_name: latest_version, 85 | description: release_notes.first) 86 | 87 | UI.message "Pushing new version to CocoaPods trunk" 88 | 89 | # Publish to CocoaPods trunk (This will fall-through if this release has previously been pushed) 90 | ENV["PODSPEC_PATHS"].split(",").each do |podspec| 91 | begin 92 | pod_push(path: "./#{podspec}", use_bundle_exec: true, allow_warnings: true) 93 | rescue => ex 94 | UI.error(ex) 95 | end 96 | end 97 | 98 | end 99 | 100 | desc "Using the GitHub Web API, commit the local contents of a file to main via a bot account" 101 | lane :commit_file do |options| 102 | file_path = options[:file_path] 103 | message = options[:commit_message] 104 | 105 | # Fetch the file so we can extract the sha 106 | result = Fastlane::Actions::GithubApiAction.run( 107 | server_url: "https://api.github.com", 108 | api_token: ENV["GITHUB_TOKEN"], 109 | path: "/repos/#{ENV["REPO_PATH"]}/contents/#{file_path}", 110 | error_handlers: { 111 | '*' => proc do |result| 112 | UI.error("GitHub responded with #{result[:status]}:#{result[:body]}") 113 | end 114 | } 115 | ) 116 | 117 | # Check the sha value was returned 118 | sha = result[:json]["sha"] 119 | if !sha 120 | UI.error("Was unable to find sha value for #{file_path}") 121 | end 122 | 123 | # Encode the content as base 64 124 | content = Base64.encode64(File.read("../#{file_path}")) 125 | author = {"name": "XD-CI", email: ENV["AUTHOR_EMAIL"]} 126 | 127 | # Upload the local copy of that file back up 128 | result = Fastlane::Actions::GithubApiAction.run( 129 | server_url: "https://api.github.com", 130 | api_token: ENV["GITHUB_TOKEN"], 131 | http_method: "PUT", 132 | path: "/repos/#{ENV["REPO_PATH"]}/contents/#{file_path}", 133 | body: {"message": message, "sha": sha, "content": content, "author": author, "committer": author}, 134 | error_handlers: { 135 | '*' => proc do |result| 136 | UI.error("GitHub responded with #{result[:status]}:#{result[:body]}") 137 | end 138 | } 139 | ) 140 | 141 | result 142 | end 143 | 144 | desc "Uploads a new tag to GitHub against the provided sha" 145 | lane :push_new_tag do |options| 146 | tag = options[:tag] 147 | commit_sha = options[:commit_sha] 148 | 149 | # Now that all of the new tag versions have been pushed, tag the latest commit 150 | tag_message = "Release v#{tag}" 151 | tagger = {"name": "XD-CI", email: ENV["AUTHOR_EMAIL"]} 152 | 153 | Fastlane::Actions::GithubApiAction.run( 154 | server_url: "https://api.github.com", 155 | api_token: ENV["GITHUB_TOKEN"], 156 | http_method: "POST", 157 | path: "/repos/#{ENV["REPO_PATH"]}/git/tags", 158 | body: {"message": tag_message, "tag": tag, "object": commit_sha, "type": "commit", "tagger": tagger}, 159 | error_handlers: { 160 | '*' => proc do |result| 161 | UI.error("GitHub responded with #{result[:status]}:#{result[:body]}") 162 | end 163 | } 164 | ) 165 | 166 | end 167 | --------------------------------------------------------------------------------