diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 41dd5359..a1040787 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -5,16 +5,16 @@ on: workflow_dispatch: jobs: iOS: - runs-on: macos-12 + runs-on: macos-13 env: - DEVELOPER_DIR: '/Applications/Xcode_13.4.app/Contents/Developer' + DEVELOPER_DIR: '/Applications/Xcode_15.0.1.app/Contents/Developer' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: ruby-version: '2.7' bundler-cache: true - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: Example/Pods key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }} diff --git a/Example/Podfile.lock b/Example/Podfile.lock index 55fe0608..263df2aa 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -31,4 +31,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: e2296c0f63c75329f0583a4252b3dfcb582ff009 -COCOAPODS: 1.11.3 +COCOAPODS: 1.15.2 diff --git a/Example/RSKImageCropperExample.xcodeproj/project.pbxproj b/Example/RSKImageCropperExample.xcodeproj/project.pbxproj index 3fcacbe8..8ab6b7f8 100644 --- a/Example/RSKImageCropperExample.xcodeproj/project.pbxproj +++ b/Example/RSKImageCropperExample.xcodeproj/project.pbxproj @@ -31,7 +31,7 @@ B8D73A871B9B1E73003AFA4A /* RSKInternalUtility.m in Sources */ = {isa = PBXBuildFile; fileRef = B8D73A861B9B1E73003AFA4A /* RSKInternalUtility.m */; }; B8F617661AE43CEF00499402 /* RSKImageCropperPerformanceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B8F617651AE43CEF00499402 /* RSKImageCropperPerformanceTests.m */; }; B8F617681AE4468000499402 /* RSKImageScrollViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = B8F617671AE4468000499402 /* RSKImageScrollViewTests.m */; }; - ED6AB2167F8AE24BA43C7555 /* libPods-RSKImageCropperExampleTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F1D7827AE01F17D6196BBEC0 /* libPods-RSKImageCropperExampleTests.a */; }; + D6C4891228B0D5D4681BB50D /* libPods-RSKImageCropperExampleTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2161A88867D09281BCE7A079 /* libPods-RSKImageCropperExampleTests.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -45,7 +45,10 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 01A71E3D794357135D4C815F /* Pods-RSKImageCropperExampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RSKImageCropperExampleTests.debug.xcconfig"; path = "Target Support Files/Pods-RSKImageCropperExampleTests/Pods-RSKImageCropperExampleTests.debug.xcconfig"; sourceTree = ""; }; + 2161A88867D09281BCE7A079 /* libPods-RSKImageCropperExampleTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RSKImageCropperExampleTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 46AAEE5D1B29155700E10C42 /* RSKImageCropViewController+Protected.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RSKImageCropViewController+Protected.h"; sourceTree = ""; }; + 996086E4617CD3436300F4E4 /* Pods-RSKImageCropperExampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RSKImageCropperExampleTests.release.xcconfig"; path = "Target Support Files/Pods-RSKImageCropperExampleTests/Pods-RSKImageCropperExampleTests.release.xcconfig"; sourceTree = ""; }; B803F69C1AAB0A1F004141CF /* CGGeometry+RSKImageCropper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CGGeometry+RSKImageCropper.h"; sourceTree = ""; }; B803F69D1AAB0A1F004141CF /* CGGeometry+RSKImageCropper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CGGeometry+RSKImageCropper.m"; sourceTree = ""; }; B8182F2D1ABC998B00601E43 /* LaunchScreen.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LaunchScreen.xib; sourceTree = ""; }; @@ -84,9 +87,6 @@ B8D73A861B9B1E73003AFA4A /* RSKInternalUtility.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RSKInternalUtility.m; sourceTree = ""; }; B8F617651AE43CEF00499402 /* RSKImageCropperPerformanceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RSKImageCropperPerformanceTests.m; sourceTree = ""; }; B8F617671AE4468000499402 /* RSKImageScrollViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RSKImageScrollViewTests.m; sourceTree = ""; }; - E6FAA073685BE5E78305A11F /* Pods-RSKImageCropperExampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RSKImageCropperExampleTests.release.xcconfig"; path = "Target Support Files/Pods-RSKImageCropperExampleTests/Pods-RSKImageCropperExampleTests.release.xcconfig"; sourceTree = ""; }; - F1D7827AE01F17D6196BBEC0 /* libPods-RSKImageCropperExampleTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RSKImageCropperExampleTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - FB8BD8ED2909E82D44808408 /* Pods-RSKImageCropperExampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RSKImageCropperExampleTests.debug.xcconfig"; path = "Target Support Files/Pods-RSKImageCropperExampleTests/Pods-RSKImageCropperExampleTests.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -107,7 +107,7 @@ B87A9A0919A4D2CD00D12CD4 /* XCTest.framework in Frameworks */, B87A9A0B19A4D2CD00D12CD4 /* UIKit.framework in Frameworks */, B87A9A0A19A4D2CD00D12CD4 /* Foundation.framework in Frameworks */, - ED6AB2167F8AE24BA43C7555 /* libPods-RSKImageCropperExampleTests.a in Frameworks */, + D6C4891228B0D5D4681BB50D /* libPods-RSKImageCropperExampleTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -117,8 +117,8 @@ 45354A06E09E0095C4070E12 /* Pods */ = { isa = PBXGroup; children = ( - FB8BD8ED2909E82D44808408 /* Pods-RSKImageCropperExampleTests.debug.xcconfig */, - E6FAA073685BE5E78305A11F /* Pods-RSKImageCropperExampleTests.release.xcconfig */, + 01A71E3D794357135D4C815F /* Pods-RSKImageCropperExampleTests.debug.xcconfig */, + 996086E4617CD3436300F4E4 /* Pods-RSKImageCropperExampleTests.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -151,7 +151,7 @@ B87A99F119A4D2CD00D12CD4 /* CoreGraphics.framework */, B87A99F319A4D2CD00D12CD4 /* UIKit.framework */, B87A9A0819A4D2CD00D12CD4 /* XCTest.framework */, - F1D7827AE01F17D6196BBEC0 /* libPods-RSKImageCropperExampleTests.a */, + 2161A88867D09281BCE7A079 /* libPods-RSKImageCropperExampleTests.a */, ); name = Frameworks; sourceTree = ""; @@ -276,7 +276,7 @@ isa = PBXNativeTarget; buildConfigurationList = B87A9A1B19A4D2CD00D12CD4 /* Build configuration list for PBXNativeTarget "RSKImageCropperExampleTests" */; buildPhases = ( - 60073F70E21CEB41C716D9E9 /* [CP] Check Pods Manifest.lock */, + E80ED87158857BE8135460A8 /* [CP] Check Pods Manifest.lock */, B87A9A0319A4D2CD00D12CD4 /* Sources */, B87A9A0419A4D2CD00D12CD4 /* Frameworks */, B87A9A0519A4D2CD00D12CD4 /* Resources */, @@ -352,7 +352,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 60073F70E21CEB41C716D9E9 /* [CP] Check Pods Manifest.lock */ = { + E80ED87158857BE8135460A8 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -555,12 +555,12 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CURRENT_PROJECT_VERSION = 4; + CURRENT_PROJECT_VERSION = 5; DEVELOPMENT_TEAM = J3P7YV7464; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "RSKImageCropperExample/RSKImageCropperExample-Prefix.pch"; INFOPLIST_FILE = "RSKImageCropperExample/RSKImageCropperExample-Info.plist"; - MARKETING_VERSION = 4.0.0; + MARKETING_VERSION = 4.1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.ruslanskorb.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = RSKImageCropperExample; TARGETED_DEVICE_FAMILY = 1; @@ -572,12 +572,12 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CURRENT_PROJECT_VERSION = 4; + CURRENT_PROJECT_VERSION = 5; DEVELOPMENT_TEAM = J3P7YV7464; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "RSKImageCropperExample/RSKImageCropperExample-Prefix.pch"; INFOPLIST_FILE = "RSKImageCropperExample/RSKImageCropperExample-Info.plist"; - MARKETING_VERSION = 4.0.0; + MARKETING_VERSION = 4.1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.ruslanskorb.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = RSKImageCropperExample; TARGETED_DEVICE_FAMILY = 1; @@ -587,7 +587,7 @@ }; B87A9A1C19A4D2CD00D12CD4 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = FB8BD8ED2909E82D44808408 /* Pods-RSKImageCropperExampleTests.debug.xcconfig */; + baseConfigurationReference = 01A71E3D794357135D4C815F /* Pods-RSKImageCropperExampleTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/RSKImageCropperExample.app/RSKImageCropperExample"; DEVELOPMENT_TEAM = J3P7YV7464; @@ -611,7 +611,7 @@ }; B87A9A1D19A4D2CD00D12CD4 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = E6FAA073685BE5E78305A11F /* Pods-RSKImageCropperExampleTests.release.xcconfig */; + baseConfigurationReference = 996086E4617CD3436300F4E4 /* Pods-RSKImageCropperExampleTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/RSKImageCropperExample.app/RSKImageCropperExample"; DEVELOPMENT_TEAM = J3P7YV7464; diff --git a/Example/RSKImageCropperExample.xcodeproj/xcshareddata/xcschemes/RSKImageCropperExample.xcscheme b/Example/RSKImageCropperExample.xcodeproj/xcshareddata/xcschemes/RSKImageCropperExample.xcscheme index 05e228a6..d2aa4aa0 100644 --- a/Example/RSKImageCropperExample.xcodeproj/xcshareddata/xcschemes/RSKImageCropperExample.xcscheme +++ b/Example/RSKImageCropperExample.xcodeproj/xcshareddata/xcschemes/RSKImageCropperExample.xcscheme @@ -1,6 +1,6 @@ 1.0, >= 1.0.2) + connection_pool (>= 2.2.5) + drb i18n (>= 1.6, < 2) minitest (>= 5.1) + mutex_m tzinfo (~> 2.0) - zeitwerk (~> 2.3) - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) + addressable (2.8.6) + public_suffix (>= 2.0.2, < 6.0) algoliasearch (1.27.5) httpclient (~> 2.8, >= 2.8.3) json (>= 1.5.1) atomos (0.1.3) + base64 (0.2.0) + bigdecimal (3.1.7) claide (1.1.0) - cocoapods (1.11.3) + cocoapods (1.15.2) addressable (~> 2.8) claide (>= 1.0.2, < 2.0) - cocoapods-core (= 1.11.3) + cocoapods-core (= 1.15.2) cocoapods-deintegrate (>= 1.0.3, < 2.0) - cocoapods-downloader (>= 1.4.0, < 2.0) + cocoapods-downloader (>= 2.1, < 3.0) cocoapods-plugins (>= 1.0.0, < 2.0) cocoapods-search (>= 1.0.0, < 2.0) - cocoapods-trunk (>= 1.4.0, < 2.0) + cocoapods-trunk (>= 1.6.0, < 2.0) cocoapods-try (>= 1.1.0, < 2.0) colored2 (~> 3.1) escape (~> 0.0.4) @@ -32,10 +40,10 @@ GEM gh_inspector (~> 1.0) molinillo (~> 0.8.0) nap (~> 1.0) - ruby-macho (>= 1.0, < 3.0) - xcodeproj (>= 1.21.0, < 2.0) - cocoapods-core (1.11.3) - activesupport (>= 5.0, < 7) + ruby-macho (>= 2.3.0, < 3.0) + xcodeproj (>= 1.23.0, < 2.0) + cocoapods-core (1.15.2) + activesupport (>= 5.0, < 8) addressable (~> 2.8) algoliasearch (~> 1.0) concurrent-ruby (~> 1.1) @@ -45,7 +53,7 @@ GEM public_suffix (~> 4.0) typhoeus (~> 1.0) cocoapods-deintegrate (1.0.5) - cocoapods-downloader (1.6.3) + cocoapods-downloader (2.1) cocoapods-plugins (1.0.0) nap cocoapods-search (1.0.1) @@ -54,35 +62,39 @@ GEM netrc (~> 0.11) cocoapods-try (1.2.0) colored2 (3.1.2) - concurrent-ruby (1.2.2) + concurrent-ruby (1.2.3) + connection_pool (2.4.1) + drb (2.2.1) escape (0.0.4) - ethon (0.15.0) + ethon (0.16.0) ffi (>= 1.15.0) - ffi (1.15.5) + ffi (1.16.3) fourflusher (2.3.1) fui (0.5.0) gli fuzzy_match (2.0.4) gh_inspector (1.1.3) - gli (2.21.0) + gli (2.21.1) httpclient (2.8.3) - i18n (1.14.1) + i18n (1.14.4) concurrent-ruby (~> 1.0) - json (2.6.2) - minitest (5.19.0) + json (2.7.2) + minitest (5.22.3) molinillo (0.8.0) + mutex_m (0.2.0) nanaimo (0.3.0) nap (1.1.0) netrc (0.11.0) + nkf (0.2.0) public_suffix (4.0.7) - rexml (3.2.5) + rexml (3.2.6) rouge (2.0.7) ruby-macho (2.5.1) - typhoeus (1.4.0) + typhoeus (1.4.1) ethon (>= 0.9.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - xcodeproj (1.21.0) + xcodeproj (1.24.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) @@ -91,15 +103,14 @@ GEM rexml (~> 3.2.4) xcpretty (0.3.0) rouge (~> 2.0.7) - zeitwerk (2.6.11) PLATFORMS ruby DEPENDENCIES - cocoapods (= 1.11.3) + cocoapods (= 1.15.2) fui (= 0.5.0) xcpretty (= 0.3.0) BUNDLED WITH - 1.17.2 + 2.4.10 diff --git a/Makefile b/Makefile index 0da5bbb3..fe175edf 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ WORKSPACE = Example/RSKImageCropperExample.xcworkspace SCHEME = RSKImageCropperExample CONFIGURATION = Release -DEVICE_HOST = platform='iOS Simulator',OS='15.5',name='iPhone 13 Pro' +DEVICE_HOST = platform='iOS Simulator',OS='17.0.1',name='iPhone 15 Pro' .PHONY: all build ci clean test lint diff --git a/README.md b/README.md index 080fccdf..fd97f68d 100644 --- a/README.md +++ b/README.md @@ -188,6 +188,10 @@ Then implement the data source functions. Build and run the `RSKImageCropperExample` project in Xcode to see `RSKImageCropper` in action. Have fun. Fork and send pull requests. Figure out hooks for customization. +## Privacy + +`RSKImageCropper` doesn't require a privacy manifest. According to [information received from Apple](https://developer.apple.com/forums/thread/746481?answerId=782256022#782256022), we should avoid adding an empty privacy manifest to our frameworks. + ## Contact Ruslan Skorb diff --git a/RSKImageCropper.podspec b/RSKImageCropper.podspec index 5fd72fcf..14bef328 100644 --- a/RSKImageCropper.podspec +++ b/RSKImageCropper.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'RSKImageCropper' - s.version = '4.0.0' + s.version = '4.1.0' s.summary = 'An image cropper for iOS like in the Contacts app with support for landscape orientation.' s.homepage = 'https://github.com/ruslanskorb/RSKImageCropper' s.license = { :type => 'MIT', :file => 'LICENSE' } diff --git a/RSKImageCropper.xcodeproj/project.pbxproj b/RSKImageCropper.xcodeproj/project.pbxproj index fe2efcf9..cc5a6345 100644 --- a/RSKImageCropper.xcodeproj/project.pbxproj +++ b/RSKImageCropper.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -151,7 +151,8 @@ A5BE39341B32B49D00ECDF88 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1340; + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1520; TargetAttributes = { A5BE393C1B32B49D00ECDF88 = { CreatedOnToolsVersion = 6.3.2; @@ -235,10 +236,11 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 3.0.2; + CURRENT_PROJECT_VERSION = 4.1.0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -295,10 +297,11 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 3.0.2; + CURRENT_PROJECT_VERSION = 4.1.0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -320,17 +323,24 @@ A5BE39541B32B49D00ECDF88 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - CURRENT_PROJECT_VERSION = 4; + CODE_SIGN_IDENTITY = ""; + CURRENT_PROJECT_VERSION = 5; DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 3.0.2; - DYLIB_CURRENT_VERSION = 3.0.2; + DYLIB_COMPATIBILITY_VERSION = 4.1.0; + DYLIB_CURRENT_VERSION = 4.1.0; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; INFOPLIST_FILE = Framework/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 4.0.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 4.1.0; MODULEMAP_FILE = "$(SRCROOT)/RSKImageCropper/RSKImageCropper.modulemap"; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu99 gnu++11"; PRODUCT_BUNDLE_IDENTIFIER = "com.ruslanskorb.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = RSKImageCropper; SKIP_INSTALL = YES; @@ -340,17 +350,24 @@ A5BE39551B32B49D00ECDF88 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; - CURRENT_PROJECT_VERSION = 4; + CODE_SIGN_IDENTITY = ""; + CURRENT_PROJECT_VERSION = 5; DEFINES_MODULE = YES; - DYLIB_COMPATIBILITY_VERSION = 3.0.2; - DYLIB_CURRENT_VERSION = 3.0.2; + DYLIB_COMPATIBILITY_VERSION = 4.1.0; + DYLIB_CURRENT_VERSION = 4.1.0; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; INFOPLIST_FILE = Framework/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - MARKETING_VERSION = 4.0.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 4.1.0; MODULEMAP_FILE = "$(SRCROOT)/RSKImageCropper/RSKImageCropper.modulemap"; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu99 gnu++11"; PRODUCT_BUNDLE_IDENTIFIER = "com.ruslanskorb.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = RSKImageCropper; SKIP_INSTALL = YES; diff --git a/RSKImageCropper.xcodeproj/xcshareddata/xcschemes/RSKImageCropper.xcscheme b/RSKImageCropper.xcodeproj/xcshareddata/xcschemes/RSKImageCropper.xcscheme index 545dcc55..e2139066 100755 --- a/RSKImageCropper.xcodeproj/xcshareddata/xcschemes/RSKImageCropper.xcscheme +++ b/RSKImageCropper.xcodeproj/xcshareddata/xcschemes/RSKImageCropper.xcscheme @@ -1,6 +1,6 @@ -NS_ASSUME_NONNULL_BEGIN +NS_HEADER_AUDIT_BEGIN(nullability, sendability) @protocol RSKImageCropViewControllerDataSource; @protocol RSKImageCropViewControllerDelegate; @@ -38,6 +38,7 @@ typedef NS_ENUM(NSUInteger, RSKImageCropMode) { RSKImageCropModeCustom }; +NS_SWIFT_UI_ACTOR @interface RSKImageCropViewController : UIViewController /** @@ -58,7 +59,7 @@ typedef NS_ENUM(NSUInteger, RSKImageCropMode) { /** Zooms to a specific area of the image so that it is visible. - @param rect A rectangle defining an area of the image. + @param rect A rectangle defining an area of the image. The rectangle should be in the coordinate space of the image view controller's view. @param animated YES if the scrolling should be animated, NO if it should be immediate. */ - (void)zoomToRect:(CGRect)rect animated:(BOOL)animated; @@ -299,6 +300,7 @@ typedef NS_ENUM(NSUInteger, RSKImageCropMode) { /** The `RSKImageCropViewControllerDataSource` protocol is adopted by an object that provides a custom rect and a custom path for the mask and a custom movement rect for the image. */ +NS_SWIFT_UI_ACTOR @protocol RSKImageCropViewControllerDataSource /** @@ -333,6 +335,7 @@ typedef NS_ENUM(NSUInteger, RSKImageCropMode) { /** The `RSKImageCropViewControllerDelegate` protocol defines messages sent to a image crop view controller delegate when crop image was canceled or the original image was cropped. */ +NS_SWIFT_UI_ACTOR @protocol RSKImageCropViewControllerDelegate /** @@ -359,4 +362,4 @@ typedef NS_ENUM(NSUInteger, RSKImageCropMode) { @end -NS_ASSUME_NONNULL_END +NS_HEADER_AUDIT_END(nullability, sendability) diff --git a/RSKImageCropper/RSKImageCropViewController.m b/RSKImageCropper/RSKImageCropViewController.m index ef8416d0..97cc3529 100644 --- a/RSKImageCropper/RSKImageCropViewController.m +++ b/RSKImageCropper/RSKImageCropViewController.m @@ -194,7 +194,7 @@ - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; - if (!self.imageScrollView.zoomView) { + if (!self.imageScrollView.image) { [self displayImage]; } } @@ -552,7 +552,7 @@ - (void)setCropMode:(RSKImageCropMode)cropMode if (_cropMode != cropMode) { _cropMode = cropMode; - if (self.imageScrollView.zoomView) { + if (self.imageScrollView.image) { [self reset:NO]; } } @@ -577,10 +577,14 @@ - (void)setMaskPath:(UIBezierPath *)maskPath [clipPath appendPath:maskPath]; clipPath.usesEvenOddFillRule = YES; - CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"]; - pathAnimation.duration = [CATransaction animationDuration]; - pathAnimation.timingFunction = [CATransaction animationTimingFunction]; - [self.maskLayer addAnimation:pathAnimation forKey:@"path"]; + CAAnimation *animation = (CAAnimation *)[self.overlayView actionForLayer:self.overlayView.layer forKey:@"backgroundColor"]; + if ([animation isKindOfClass:[CAAnimation class]]) { + CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"]; + pathAnimation.duration = animation.duration; + pathAnimation.timingFunction = animation.timingFunction; + + [self.maskLayer addAnimation:pathAnimation forKey:@"path"]; + } self.maskLayer.path = [clipPath CGPath]; } @@ -648,7 +652,7 @@ - (void)handleRotation:(UIRotationGestureRecognizer *)gestureRecognizer - (void)zoomToRect:(CGRect)rect animated:(BOOL)animated { - [self.imageScrollView zoomToRect:rect animated:animated]; + [self.imageScrollView zoomToRect:[self.imageScrollView convertRect:rect fromView:self.view] animated:animated]; } #pragma mark - Public @@ -681,7 +685,7 @@ - (void)reset:(BOOL)animated - (void)resetContentOffset { CGSize boundsSize = self.imageScrollView.bounds.size; - CGRect frameToCenter = self.imageScrollView.zoomView.frame; + CGRect frameToCenter = self.imageScrollView.imageFrame; CGPoint contentOffset; if (CGRectGetWidth(frameToCenter) > boundsSize.width) { @@ -753,7 +757,7 @@ - (NSArray *)intersectionPointsOfLineSegment:(RSKLineSegment)lineSegment withRec - (void)displayImage { if (self.originalImage) { - [self.imageScrollView displayImage:self.originalImage]; + self.imageScrollView.image = self.originalImage; [self reset:NO]; if ([self.delegate respondsToSelector:@selector(imageCropViewControllerDidDisplayImage:)]) { @@ -886,10 +890,11 @@ - (void)updateMaskRect CGSize maskSize = CGSizeMake(diameter, diameter); - self.maskRect = CGRectMake((viewWidth - maskSize.width) * 0.5f, - (viewHeight - maskSize.height) * 0.5f, - maskSize.width, - maskSize.height); + CGRect maskRect = CGRectMake(floor((viewWidth - maskSize.width) * 0.5f), + floor((viewHeight - maskSize.height) * 0.5f), + maskSize.width, + maskSize.height); + self.maskRect = CGRectIntegral(maskRect); break; } case RSKImageCropModeSquare: { @@ -905,10 +910,11 @@ - (void)updateMaskRect CGSize maskSize = CGSizeMake(length, length); - self.maskRect = CGRectMake((viewWidth - maskSize.width) * 0.5f, - (viewHeight - maskSize.height) * 0.5f, - maskSize.width, - maskSize.height); + CGRect maskRect = CGRectMake(floor((viewWidth - maskSize.width) * 0.5f), + floor((viewHeight - maskSize.height) * 0.5f), + maskSize.width, + maskSize.height); + self.maskRect = CGRectIntegral(maskRect); break; } case RSKImageCropModeCustom: { diff --git a/RSKImageCropper/RSKImageScrollView.h b/RSKImageCropper/RSKImageScrollView.h index c1306c50..3e76b354 100755 --- a/RSKImageCropper/RSKImageScrollView.h +++ b/RSKImageCropper/RSKImageScrollView.h @@ -1,7 +1,7 @@ /* File: RSKImageScrollView.h Abstract: Centers image within the scroll view and configures image sizing and display. - Version: 1.3 modified by Ruslan Skorb on 8/24/14. + Version: 1.4 modified by Ruslan Skorb on 4/7/24. Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. ("Apple") in consideration of your agreement to the following @@ -42,23 +42,87 @@ POSSIBILITY OF SUCH DAMAGE. Copyright (C) 2012 Apple Inc. All Rights Reserved. + Copyright (C) 2014-present Ruslan Skorb. All Rights Reserved. */ #import -NS_ASSUME_NONNULL_BEGIN +NS_HEADER_AUDIT_BEGIN(nullability, sendability) @protocol RSKImageScrollViewDelegate; +/** + A view that allows the scrolling and zooming of its image. + */ +NS_SWIFT_UI_ACTOR @interface RSKImageScrollView : UIScrollView -@property (nonatomic, nullable, weak) id imageScrollViewDelegate; -@property (nonatomic, nullable, strong) UIImageView *zoomView; +/** + A Boolean value that determines whether the image will always fill the available space. Default value is `NO`. + */ @property (nonatomic, assign) BOOL aspectFill; -- (void)displayImage:(UIImage *)image; +/** + An image for scrolling and zooming. Default value is `nil`. + */ +@property (nonatomic, nullable, strong) UIImage *image; + +/** + The color of the background behind the image. Default value is `nil`, which results in a transparent color. + + @discussion Changes to this property can be animated. + */ +@property (nonatomic, nullable, strong) UIColor *imageBackgroundColor; + +/** + The current frame of the image in the coordinate space of the image scroll view. + */ +@property (nonatomic, readonly) CGRect imageFrame; + +/** + The delegate of the image scroll view. + + @discussion The delegate must adopt the `RSKImageScrollViewDelegate` protocol. The `RSKImageScrollView` class, which doesn’t retain the delegate, invokes each protocol method the delegate implements. + */ +@property (nonatomic, nullable, weak) id imageScrollViewDelegate; + +/** + The logical dimensions, in points, of the image. Default value is `CGRectZero`. + */ +@property (nonatomic, assign) CGSize imageSize; + +/** + Sets the current scale factor applied to the image and offset from the image’s origin to the initial value. + + @param animated `YES` to animate the transition to the new scale and content offset, `NO` to make the transition immediate. + */ +- (void)setInitialZoomScaleAndContentOffsetAnimated:(BOOL)animated; + +/** + Zooms to a specific location in the image so that it’s visible in the image scroll view. + + @param location A point defining a location in the image. The point should be in the coordinate space of the image scroll view. + @param animated `YES` if the scrolling should be animated, `NO` if it should be immediate. + */ +- (void)zoomToLocation:(CGPoint)location animated:(BOOL)animated; + +/** + Zooms to a specific area of the image so that it’s visible in the image scroll view. + + @param rect A rectangle defining an area of the image. The rectangle should be in the coordinate space of the image scroll view. + @param animated `YES` if the scrolling should be animated, `NO` if it should be immediate. + */ +- (void)zoomToRect:(CGRect)rect animated:(BOOL)animated; + +@end + +@interface RSKImageScrollView (Deprecated) + +@property (nonatomic, nullable, strong) UIImageView *zoomView __deprecated_msg("Please use 'image' and 'imageFrame' getters instead."); + +- (void)displayImage:(UIImage *)image __deprecated_msg("Please use 'image' setter instead."); @end -NS_ASSUME_NONNULL_END +NS_HEADER_AUDIT_END(nullability, sendability) diff --git a/RSKImageCropper/RSKImageScrollView.m b/RSKImageCropper/RSKImageScrollView.m index de4f5ab4..d265dadf 100755 --- a/RSKImageCropper/RSKImageScrollView.m +++ b/RSKImageCropper/RSKImageScrollView.m @@ -1,7 +1,7 @@ /* File: RSKImageScrollView.m Abstract: Centers image within the scroll view and configures image sizing and display. - Version: 1.3 modified by Ruslan Skorb on 8/24/14. + Version: 1.4 modified by Ruslan Skorb on 4/7/24. Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. ("Apple") in consideration of your agreement to the following @@ -42,6 +42,7 @@ OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF POSSIBILITY OF SUCH DAMAGE. Copyright (C) 2012 Apple Inc. All Rights Reserved. + Copyright (C) 2014-present Ruslan Skorb. All Rights Reserved. */ @@ -55,7 +56,8 @@ OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF @interface RSKImageScrollView () { CGSize _imageSize; - + UIImageView *_imageView; + CGPoint _pointToCenterAfterResize; CGFloat _scaleToRestoreAfterResize; } @@ -70,11 +72,14 @@ - (id)initWithFrame:(CGRect)frame if (self) { _aspectFill = NO; + _imageView = [[UIImageView alloc] init]; self.showsVerticalScrollIndicator = NO; self.showsHorizontalScrollIndicator = NO; self.scrollsToTop = NO; self.decelerationRate = UIScrollViewDecelerationRateFast; self.delegate = self; + + [self addSubview:_imageView]; } return self; } @@ -83,7 +88,7 @@ - (void)didAddSubview:(UIView *)subview { [super didAddSubview:subview]; - [self centerZoomView]; + [self centerImageView]; } - (void)setAspectFill:(BOOL)aspectFill @@ -91,18 +96,87 @@ - (void)setAspectFill:(BOOL)aspectFill if (_aspectFill != aspectFill) { _aspectFill = aspectFill; - if (_zoomView) { + if (_imageView.image) { [self setMaxMinZoomScalesForCurrentBounds]; if (self.zoomScale < self.minimumZoomScale) { self.zoomScale = self.minimumZoomScale; + } else if (self.zoomScale > self.maximumZoomScale) { + self.zoomScale = self.maximumZoomScale; } } } } +- (UIImage *)image +{ + return _imageView.image; +} + +- (void)setImage:(UIImage *)image +{ + UIImage *oldImage = _imageView.image; + _imageView.image = image; + + if (oldImage || !CGSizeEqualToSize(_imageSize, image.size)) { + self.imageSize = image.size; + } +} + +- (UIColor *)imageBackgroundColor +{ + return _imageView.backgroundColor; +} + +- (void)setImageBackgroundColor:(UIColor *)imageBackgroundColor +{ + _imageView.backgroundColor = imageBackgroundColor; +} + +- (CGRect)imageFrame +{ + return _imageView.frame; +} + +- (void)setImageSize:(CGSize)imageSize +{ + _imageSize = imageSize; + + self.zoomScale = 1.0f; + _imageView.frame = CGRectMake(0.0f, 0.0f, imageSize.width, imageSize.height); + self.contentSize = imageSize; + [self setMaxMinZoomScalesForCurrentBounds]; + [self setInitialZoomScale]; + [self setInitialContentOffset]; + [self centerImageView]; +} + +- (void)setInitialZoomScaleAndContentOffsetAndCenterImageView +{ + [self setInitialZoomScale]; + [self setInitialContentOffset]; + [self centerImageView]; +} + +- (void)setInitialZoomScaleAndContentOffsetAnimated:(BOOL)animated +{ + if (animated) { + UIViewAnimationOptions options = UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationCurveEaseInOut; + [UIView animateWithDuration:0.4f delay:0.0f options:options animations:^{ + [self setInitialZoomScaleAndContentOffsetAndCenterImageView]; + } completion:nil]; + } else { + [self setInitialZoomScaleAndContentOffsetAndCenterImageView]; + } +} + - (void)setFrame:(CGRect)frame { + if (CGSizeEqualToSize(self.contentSize, CGSizeZero)) { + [super setFrame:frame]; + return; + } + BOOL sizeChanging = !CGSizeEqualToSize(frame.size, self.frame.size); if (sizeChanging) { @@ -115,98 +189,123 @@ - (void)setFrame:(CGRect)frame [self recoverFromResizing]; } - [self centerZoomView]; + [self centerImageView]; +} + +- (void)zoomToLocation:(CGPoint)location animated:(BOOL)animated +{ + CGPoint locationInImageView = [_imageView convertPoint:location fromView:self]; + CGSize size = CGSizeMake(self.bounds.size.width / self.maximumZoomScale, + self.bounds.size.height / self.maximumZoomScale); + CGPoint origin = CGPointMake(locationInImageView.x - size.width * 0.5f, + locationInImageView.y - size.height * 0.5f); + CGRect rect = CGRectMake(origin.x, origin.y, size.width, size.height); + + [self zoomToRect:rect animated:animated]; +} + +- (void)zoomToRect:(CGRect)rect animated:(BOOL)animated +{ + rect = [_imageView convertRect:rect fromView:self]; + + [super zoomToRect:rect animated:animated]; } #pragma mark - UIScrollViewDelegate - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView { - return _zoomView; + return _imageView; +} + +- (void)scrollViewDidScroll:(UIScrollView *)scrollView +{ + if ([self.imageScrollViewDelegate respondsToSelector:@selector(imageScrollViewDidScroll)]) { + [self.imageScrollViewDelegate imageScrollViewDidScroll]; + } } - (void)scrollViewDidZoom:(__unused UIScrollView *)scrollView { - [self centerZoomView]; + [self centerImageView]; } - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { - [self.imageScrollViewDelegate imageScrollViewWillBeginDragging]; + if ([self.imageScrollViewDelegate respondsToSelector:@selector(imageScrollViewWillBeginDragging)]) { + [self.imageScrollViewDelegate imageScrollViewWillBeginDragging]; + } } - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { - [self.imageScrollViewDelegate imageScrollViewDidEndDragging:decelerate]; + if ([self.imageScrollViewDelegate respondsToSelector:@selector(imageScrollViewDidEndDragging:)]) { + [self.imageScrollViewDelegate imageScrollViewDidEndDragging:decelerate]; + } } - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { - [self.imageScrollViewDelegate imageScrollViewDidEndDecelerating]; + if ([self.imageScrollViewDelegate respondsToSelector:@selector(imageScrollViewDidEndDecelerating)]) { + [self.imageScrollViewDelegate imageScrollViewDidEndDecelerating]; + } } - (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view { - [self.imageScrollViewDelegate imageScrollViewWillBeginZooming]; + if ([self.imageScrollViewDelegate respondsToSelector:@selector(imageScrollViewWillBeginZooming)]) { + [self.imageScrollViewDelegate imageScrollViewWillBeginZooming]; + } } - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale { - [self.imageScrollViewDelegate imageScrollViewDidEndZooming]; + if ([self.imageScrollViewDelegate respondsToSelector:@selector(imageScrollViewDidEndZooming)]) { + [self.imageScrollViewDelegate imageScrollViewDidEndZooming]; + } } -#pragma mark - Center zoomView within scrollView +#pragma mark - Center imageView within scrollView -- (void)centerZoomView +- (void)centerImageView { - // center zoomView as it becomes smaller than the size of the screen + // center imageView as it becomes smaller than the size of the screen + + CGFloat top = 0.0f; + CGFloat left = 0.0f; - CGFloat top = 0; - CGFloat left = 0; - // center vertically if (self.contentSize.height < CGRectGetHeight(self.bounds)) { top = (CGRectGetHeight(self.bounds) - self.contentSize.height) * 0.5f; } - + // center horizontally if (self.contentSize.width < CGRectGetWidth(self.bounds)) { left = (CGRectGetWidth(self.bounds) - self.contentSize.width) * 0.5f; } - self.contentInset = UIEdgeInsetsMake(top, left, top, left); + UIEdgeInsets contentInset = UIEdgeInsetsMake(top, left, top, left); + + if (!UIEdgeInsetsEqualToEdgeInsets(self.contentInset, contentInset)) { + self.contentInset = contentInset; + } } #pragma mark - Configure scrollView to display new image -- (void)displayImage:(UIImage *)image +- (void)setMaxMinZoomScalesForCurrentBounds { - // clear view for the previous image - [_zoomView removeFromSuperview]; - _zoomView = nil; - - // reset our zoomScale to 1.0 before doing any further calculations - self.zoomScale = 1.0; - - // make views to display the new image - _zoomView = [[UIImageView alloc] initWithImage:image]; - [self addSubview:_zoomView]; + if (CGSizeEqualToSize(self.bounds.size, CGSizeZero)) { + return; + } - [self configureForImageSize:image.size]; -} + if (CGSizeEqualToSize(_imageSize, CGSizeZero)) { + self.maximumZoomScale = 1.0f; + self.minimumZoomScale = 1.0f; -- (void)configureForImageSize:(CGSize)imageSize -{ - _imageSize = imageSize; - self.contentSize = imageSize; - [self setMaxMinZoomScalesForCurrentBounds]; - [self setInitialZoomScale]; - [self setInitialContentOffset]; - self.contentInset = UIEdgeInsetsZero; -} - -- (void)setMaxMinZoomScalesForCurrentBounds -{ + return; + } + CGSize boundsSize = self.bounds.size; // calculate min/max zoomscale @@ -214,17 +313,17 @@ - (void)setMaxMinZoomScalesForCurrentBounds CGFloat yScale = boundsSize.height / _imageSize.height; // the scale needed to perfectly fit the image height-wise CGFloat minScale; - if (!self.aspectFill) { - minScale = MIN(xScale, yScale); // use minimum of these to allow the image to become fully visible - } else { + if (_aspectFill) { minScale = MAX(xScale, yScale); // use maximum of these to allow the image to fill the screen + } else { + minScale = MIN(xScale, yScale); // use minimum of these to allow the image to become fully visible } CGFloat maxScale = MAX(xScale, yScale); // Image must fit/fill the screen, even if its size is smaller. - CGFloat xImageScale = maxScale*_imageSize.width / boundsSize.width; - CGFloat yImageScale = maxScale*_imageSize.height / boundsSize.height; + CGFloat xImageScale = maxScale * _imageSize.width / boundsSize.width; + CGFloat yImageScale = maxScale * _imageSize.height / boundsSize.height; CGFloat maxImageScale = MAX(xImageScale, yImageScale); @@ -242,31 +341,27 @@ - (void)setMaxMinZoomScalesForCurrentBounds - (void)setInitialZoomScale { - CGSize boundsSize = self.bounds.size; - CGFloat xScale = boundsSize.width / _imageSize.width; // the scale needed to perfectly fit the image width-wise - CGFloat yScale = boundsSize.height / _imageSize.height; // the scale needed to perfectly fit the image height-wise - CGFloat scale = MAX(xScale, yScale); - self.zoomScale = scale; + if (self.zoomScale != self.minimumZoomScale) { + self.zoomScale = self.minimumZoomScale; + } } - (void)setInitialContentOffset { CGSize boundsSize = self.bounds.size; - CGRect frameToCenter = self.zoomView.frame; + CGRect frameToCenter = _imageView.frame; - CGPoint contentOffset; + CGPoint contentOffset = self.contentOffset; if (CGRectGetWidth(frameToCenter) > boundsSize.width) { contentOffset.x = (CGRectGetWidth(frameToCenter) - boundsSize.width) * 0.5f; - } else { - contentOffset.x = 0; } if (CGRectGetHeight(frameToCenter) > boundsSize.height) { contentOffset.y = (CGRectGetHeight(frameToCenter) - boundsSize.height) * 0.5f; - } else { - contentOffset.y = 0; } - [self setContentOffset:contentOffset]; + if (!CGPointEqualToPoint(self.contentOffset, contentOffset)) { + self.contentOffset = contentOffset; + } } #pragma mark - @@ -276,13 +371,13 @@ - (void)setInitialContentOffset - (void)prepareToResize { - if (_zoomView == nil) { + if (_imageView == nil) { return; } CGPoint boundsCenter = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds)); - _pointToCenterAfterResize = [self convertPoint:boundsCenter toView:self.zoomView]; - + _pointToCenterAfterResize = [self convertPoint:boundsCenter toView:_imageView]; + _scaleToRestoreAfterResize = self.zoomScale; // If we're at the minimum zoom scale, preserve that by returning 0, which will be converted to the minimum @@ -293,7 +388,7 @@ - (void)prepareToResize - (void)recoverFromResizing { - if (_zoomView == nil) { + if (_imageView == nil) { return; } @@ -306,12 +401,12 @@ - (void)recoverFromResizing // Step 2: restore center point, first making sure it is within the allowable range. // 2a: convert our desired center point back to our own coordinate space - CGPoint boundsCenter = [self convertPoint:_pointToCenterAfterResize fromView:self.zoomView]; - + CGPoint boundsCenter = [self convertPoint:_pointToCenterAfterResize fromView:_imageView]; + // 2b: calculate the content offset that would yield that center point - CGPoint offset = CGPointMake(boundsCenter.x - self.bounds.size.width / 2.0, - boundsCenter.y - self.bounds.size.height / 2.0); - + CGPoint offset = CGPointMake(boundsCenter.x - self.bounds.size.width * 0.5f, + boundsCenter.y - self.bounds.size.height * 0.5f); + // 2c: restore offset, adjusted to be within the allowable range CGPoint maxOffset = [self maximumContentOffset]; CGPoint minOffset = [self minimumContentOffset]; @@ -322,6 +417,15 @@ - (void)recoverFromResizing realMaxOffset = MIN(maxOffset.y, offset.y); offset.y = MAX(minOffset.y, realMaxOffset); + if (self.contentSize.height < self.bounds.size.height) { + + offset.y = -(self.bounds.size.height - self.contentSize.height) * 0.5f; + } + if (self.contentSize.width < self.bounds.size.width) { + + offset.x = -(self.bounds.size.width - self.contentSize.width) * 0.5f; + } + self.contentOffset = offset; } @@ -338,3 +442,34 @@ - (CGPoint)minimumContentOffset } @end + +@implementation RSKImageScrollView (Deprecated) + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + +- (UIImageView *)zoomView { + + if (!_imageView.image) { + return nil; + } + return _imageView; +} + +- (void)setZoomView:(UIImageView *)zoomView { + + [_imageView removeFromSuperview]; + _imageView = zoomView; + if (_imageView) { + [self addSubview:_imageView]; + } +} + +- (void)displayImage:(UIImage *)image +{ + self.image = image; +} + +#pragma clang diagnostic pop + +@end diff --git a/RSKImageCropper/RSKImageScrollViewDelegate.h b/RSKImageCropper/RSKImageScrollViewDelegate.h index 12ef382b..86019a59 100644 --- a/RSKImageCropper/RSKImageScrollViewDelegate.h +++ b/RSKImageCropper/RSKImageScrollViewDelegate.h @@ -26,12 +26,44 @@ NS_ASSUME_NONNULL_BEGIN +/** + The interface for the delegate of an image scroll view. + */ +NS_SWIFT_UI_ACTOR @protocol RSKImageScrollViewDelegate +@optional + +/** + Tells the delegate when the user scrolls the image within the image scroll view. + */ +- (void)imageScrollViewDidScroll; + +/** + Tells the delegate when the image scroll view is about to start scrolling the image. + */ - (void)imageScrollViewWillBeginDragging; + +/** + Tells the delegate when dragging ended in the image scroll view. + + @param willDecelerate `YES` if the scrolling movement will continue, but decelerate, after a touch-up gesture during a dragging operation. If the value is `NO`, scrolling stops immediately upon touch-up. + */ - (void)imageScrollViewDidEndDragging:(BOOL)willDecelerate; + +/** + Tells the delegate that the image scroll view ended decelerating the scrolling movement. + */ - (void)imageScrollViewDidEndDecelerating; + +/** + Tells the delegate that zooming of the image in the image scroll view is about to commence. + */ - (void)imageScrollViewWillBeginZooming; + +/** + Tells the delegate when zooming of the image in the image scroll view completed. + */ - (void)imageScrollViewDidEndZooming; @end diff --git a/RSKImageCropper/RSKInternalUtility.h b/RSKImageCropper/RSKInternalUtility.h index de6e1cd7..fe608cf3 100644 --- a/RSKImageCropper/RSKInternalUtility.h +++ b/RSKImageCropper/RSKInternalUtility.h @@ -24,6 +24,8 @@ #import +NS_HEADER_AUDIT_BEGIN(nullability, sendability) + /** Returns a localized version of the string designated by the specified key and residing in the RSKImageCropper table. @@ -39,11 +41,13 @@ FOUNDATION_EXPORT NSString * RSKLocalizedString(NSString *key, NSString *comment /** Returns the NSBundle object for returning localized strings. - @return The NSBundle object for returning localized strings. + @return The NSBundle object for returning localized strings, or nil if the bundle is not found on the system. @discussion We assume a convention of a bundle named RSKImageCropperStrings.bundle, otherwise we return the bundle associated with the RSKInternalUtility class. */ -+ (NSBundle *)bundleForStrings; ++ (nullable NSBundle *)bundleForStrings; @end + +NS_HEADER_AUDIT_END(nullability, sendability) diff --git a/RSKImageCropper/RSKTouchView.h b/RSKImageCropper/RSKTouchView.h index 69b7f90a..9a884a9e 100755 --- a/RSKImageCropper/RSKTouchView.h +++ b/RSKImageCropper/RSKTouchView.h @@ -24,8 +24,13 @@ #import +NS_HEADER_AUDIT_BEGIN(nullability, sendability) + +NS_SWIFT_UI_ACTOR @interface RSKTouchView : UIView -@property (weak, nonatomic) UIView *receiver; +@property (weak, nonatomic, nullable) UIView *receiver; @end + +NS_HEADER_AUDIT_END(nullability, sendability) diff --git a/RSKImageCropper/UIImage+RSKImageCropper.h b/RSKImageCropper/UIImage+RSKImageCropper.h index bd3bc0dd..4e6975e8 100755 --- a/RSKImageCropper/UIImage+RSKImageCropper.h +++ b/RSKImageCropper/UIImage+RSKImageCropper.h @@ -24,12 +24,16 @@ #import +NS_ASSUME_NONNULL_BEGIN + @interface UIImage (RSKImageCropper) // Fix the orientation of the image. -- (UIImage *)fixOrientation; +- (nullable UIImage *)fixOrientation; // Rotate the image clockwise around the center by the angle, in radians. -- (UIImage *)rotateByAngle:(CGFloat)angleInRadians; +- (nullable UIImage *)rotateByAngle:(CGFloat)angleInRadians; @end + +NS_ASSUME_NONNULL_END