├── .gitignore ├── .swift-version ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── AZImagePreview.podspec ├── AZImagePreviewExample ├── .DS_Store ├── AZImagePreviewExample.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── AZImagePreviewExample.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── AZImagePreviewExample │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── ic_file_download.imageset │ │ ├── Contents.json │ │ ├── ic_file_download.png │ │ ├── ic_file_download_2x.png │ │ └── ic_file_download_3x.png │ ├── image_1.imageset │ │ ├── Contents.json │ │ └── adam-kool-11868.jpg │ ├── image_2.imageset │ │ ├── Contents.json │ │ └── adam-wilson-306570.jpg │ ├── image_3.imageset │ │ ├── Contents.json │ │ └── hamburger-arts-260481.jpg │ ├── image_4.imageset │ │ ├── Contents.json │ │ └── matthew-henry-213827.jpg │ ├── image_5.imageset │ │ ├── Contents.json │ │ └── andrew-neel-308138.jpg │ ├── image_6.imageset │ │ ├── Contents.json │ │ └── anton-darius-sollers-280400.jpg │ ├── image_7.imageset │ │ ├── Contents.json │ │ └── aleksandr-ledogorov-212542.jpg │ ├── image_8.imageset │ │ ├── Contents.json │ │ └── andrew-pons-57133.jpg │ └── image_9.imageset │ │ ├── Contents.json │ │ └── anthony-delanoix-152872.jpg │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── CircleImageView.swift │ ├── CollectionViewController.swift │ ├── Info.plist │ ├── TableViewController.swift │ └── ViewController.swift ├── LICENSE ├── Package.swift ├── README.md ├── Screenshots ├── sc1.gif └── sc2.gif └── Sources ├── .DS_Store └── AZImagePreview ├── AZImagePresenterViewController.swift ├── AZPreviewImageViewDelegate.swift └── PannableScrollView.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | .build/ 41 | 42 | # CocoaPods 43 | # 44 | # We recommend against adding the Pods directory to your .gitignore. However 45 | # you should judge for yourself, the pros and cons are mentioned at: 46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 47 | # 48 | # Pods/ 49 | 50 | # Carthage 51 | # 52 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 53 | # Carthage/Checkouts 54 | 55 | Carthage/Build 56 | 57 | # fastlane 58 | # 59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 60 | # screenshots whenever they are needed. 61 | # For more information about the recommended setup visit: 62 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 63 | 64 | fastlane/report.xml 65 | fastlane/Preview.html 66 | fastlane/screenshots 67 | fastlane/test_output 68 | 69 | \.DS_Store 70 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 5.0 -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /AZImagePreview.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "AZImagePreview" 3 | s.version = "1.2.0" 4 | s.summary = "iOS Framework that makes it easy to preview images on any UIImageView." 5 | s.homepage = "https://github.com/Minitour/AZImagePreview" 6 | s.license = "MIT" 7 | s.author = { "Antonio Zaitoun" => "tony.z.1711@gmail.com" } 8 | s.platform = :ios, "9.0" 9 | s.source = { :git => "https://github.com/Minitour/AZImagePreview.git", :tag => "#{s.version}" } 10 | s.source_files = "Sources/**/*.{swift}" 11 | end 12 | -------------------------------------------------------------------------------- /AZImagePreviewExample/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minitour/AZImagePreview/2fc7f781532ba2c969129b4831b5fc5c26ab9349/AZImagePreviewExample/.DS_Store -------------------------------------------------------------------------------- /AZImagePreviewExample/AZImagePreviewExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 52; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 9D2663EA1F15693400EB5B0E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D2663E91F15693400EB5B0E /* AppDelegate.swift */; }; 11 | 9D2663EC1F15693400EB5B0E /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D2663EB1F15693400EB5B0E /* ViewController.swift */; }; 12 | 9D2663EF1F15693400EB5B0E /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9D2663ED1F15693400EB5B0E /* Main.storyboard */; }; 13 | 9D2663F11F15693400EB5B0E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9D2663F01F15693400EB5B0E /* Assets.xcassets */; }; 14 | 9D2663F41F15693400EB5B0E /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9D2663F21F15693400EB5B0E /* LaunchScreen.storyboard */; }; 15 | 9D2664051F1572F400EB5B0E /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D2664041F1572F400EB5B0E /* TableViewController.swift */; }; 16 | 9D2664071F1577CE00EB5B0E /* CollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D2664061F1577CE00EB5B0E /* CollectionViewController.swift */; }; 17 | 9D2664091F157B8C00EB5B0E /* CircleImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D2664081F157B8C00EB5B0E /* CircleImageView.swift */; }; 18 | CF778DA32538335400D71C25 /* AZImagePreview in Frameworks */ = {isa = PBXBuildFile; productRef = CF778DA22538335400D71C25 /* AZImagePreview */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXFileReference section */ 22 | 9D2663E61F15693400EB5B0E /* AZImagePreviewExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AZImagePreviewExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 23 | 9D2663E91F15693400EB5B0E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 24 | 9D2663EB1F15693400EB5B0E /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 25 | 9D2663EE1F15693400EB5B0E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 26 | 9D2663F01F15693400EB5B0E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 27 | 9D2663F31F15693400EB5B0E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 28 | 9D2663F51F15693400EB5B0E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 29 | 9D2664041F1572F400EB5B0E /* TableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = ""; }; 30 | 9D2664061F1577CE00EB5B0E /* CollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewController.swift; sourceTree = ""; }; 31 | 9D2664081F157B8C00EB5B0E /* CircleImageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CircleImageView.swift; sourceTree = ""; }; 32 | /* End PBXFileReference section */ 33 | 34 | /* Begin PBXFrameworksBuildPhase section */ 35 | 9D2663E31F15693400EB5B0E /* Frameworks */ = { 36 | isa = PBXFrameworksBuildPhase; 37 | buildActionMask = 2147483647; 38 | files = ( 39 | CF778DA32538335400D71C25 /* AZImagePreview in Frameworks */, 40 | ); 41 | runOnlyForDeploymentPostprocessing = 0; 42 | }; 43 | /* End PBXFrameworksBuildPhase section */ 44 | 45 | /* Begin PBXGroup section */ 46 | 9D2663DD1F15693400EB5B0E = { 47 | isa = PBXGroup; 48 | children = ( 49 | 9D2663E81F15693400EB5B0E /* AZImagePreviewExample */, 50 | 9D2663E71F15693400EB5B0E /* Products */, 51 | ); 52 | sourceTree = ""; 53 | }; 54 | 9D2663E71F15693400EB5B0E /* Products */ = { 55 | isa = PBXGroup; 56 | children = ( 57 | 9D2663E61F15693400EB5B0E /* AZImagePreviewExample.app */, 58 | ); 59 | name = Products; 60 | sourceTree = ""; 61 | }; 62 | 9D2663E81F15693400EB5B0E /* AZImagePreviewExample */ = { 63 | isa = PBXGroup; 64 | children = ( 65 | 9D2663E91F15693400EB5B0E /* AppDelegate.swift */, 66 | 9D2663EB1F15693400EB5B0E /* ViewController.swift */, 67 | 9D2664041F1572F400EB5B0E /* TableViewController.swift */, 68 | 9D2664061F1577CE00EB5B0E /* CollectionViewController.swift */, 69 | 9D2664081F157B8C00EB5B0E /* CircleImageView.swift */, 70 | 9D2663ED1F15693400EB5B0E /* Main.storyboard */, 71 | 9D2663F01F15693400EB5B0E /* Assets.xcassets */, 72 | 9D2663F21F15693400EB5B0E /* LaunchScreen.storyboard */, 73 | 9D2663F51F15693400EB5B0E /* Info.plist */, 74 | ); 75 | path = AZImagePreviewExample; 76 | sourceTree = ""; 77 | }; 78 | /* End PBXGroup section */ 79 | 80 | /* Begin PBXNativeTarget section */ 81 | 9D2663E51F15693400EB5B0E /* AZImagePreviewExample */ = { 82 | isa = PBXNativeTarget; 83 | buildConfigurationList = 9D2663F81F15693400EB5B0E /* Build configuration list for PBXNativeTarget "AZImagePreviewExample" */; 84 | buildPhases = ( 85 | 9D2663E21F15693400EB5B0E /* Sources */, 86 | 9D2663E31F15693400EB5B0E /* Frameworks */, 87 | 9D2663E41F15693400EB5B0E /* Resources */, 88 | ); 89 | buildRules = ( 90 | ); 91 | dependencies = ( 92 | ); 93 | name = AZImagePreviewExample; 94 | packageProductDependencies = ( 95 | CF778DA22538335400D71C25 /* AZImagePreview */, 96 | ); 97 | productName = AZImagePreviewExample; 98 | productReference = 9D2663E61F15693400EB5B0E /* AZImagePreviewExample.app */; 99 | productType = "com.apple.product-type.application"; 100 | }; 101 | /* End PBXNativeTarget section */ 102 | 103 | /* Begin PBXProject section */ 104 | 9D2663DE1F15693400EB5B0E /* Project object */ = { 105 | isa = PBXProject; 106 | attributes = { 107 | LastSwiftUpdateCheck = 0830; 108 | LastUpgradeCheck = 0830; 109 | ORGANIZATIONNAME = "Antonio Zaitoun"; 110 | TargetAttributes = { 111 | 9D2663E51F15693400EB5B0E = { 112 | CreatedOnToolsVersion = 8.3.3; 113 | DevelopmentTeam = 35DWKWL79J; 114 | LastSwiftMigration = 1200; 115 | ProvisioningStyle = Automatic; 116 | }; 117 | }; 118 | }; 119 | buildConfigurationList = 9D2663E11F15693400EB5B0E /* Build configuration list for PBXProject "AZImagePreviewExample" */; 120 | compatibilityVersion = "Xcode 3.2"; 121 | developmentRegion = English; 122 | hasScannedForEncodings = 0; 123 | knownRegions = ( 124 | English, 125 | en, 126 | Base, 127 | ); 128 | mainGroup = 9D2663DD1F15693400EB5B0E; 129 | packageReferences = ( 130 | CF778DA12538335400D71C25 /* XCRemoteSwiftPackageReference "." */, 131 | ); 132 | productRefGroup = 9D2663E71F15693400EB5B0E /* Products */; 133 | projectDirPath = ""; 134 | projectRoot = ""; 135 | targets = ( 136 | 9D2663E51F15693400EB5B0E /* AZImagePreviewExample */, 137 | ); 138 | }; 139 | /* End PBXProject section */ 140 | 141 | /* Begin PBXResourcesBuildPhase section */ 142 | 9D2663E41F15693400EB5B0E /* Resources */ = { 143 | isa = PBXResourcesBuildPhase; 144 | buildActionMask = 2147483647; 145 | files = ( 146 | 9D2663F41F15693400EB5B0E /* LaunchScreen.storyboard in Resources */, 147 | 9D2663F11F15693400EB5B0E /* Assets.xcassets in Resources */, 148 | 9D2663EF1F15693400EB5B0E /* Main.storyboard in Resources */, 149 | ); 150 | runOnlyForDeploymentPostprocessing = 0; 151 | }; 152 | /* End PBXResourcesBuildPhase section */ 153 | 154 | /* Begin PBXSourcesBuildPhase section */ 155 | 9D2663E21F15693400EB5B0E /* Sources */ = { 156 | isa = PBXSourcesBuildPhase; 157 | buildActionMask = 2147483647; 158 | files = ( 159 | 9D2664071F1577CE00EB5B0E /* CollectionViewController.swift in Sources */, 160 | 9D2664091F157B8C00EB5B0E /* CircleImageView.swift in Sources */, 161 | 9D2663EC1F15693400EB5B0E /* ViewController.swift in Sources */, 162 | 9D2664051F1572F400EB5B0E /* TableViewController.swift in Sources */, 163 | 9D2663EA1F15693400EB5B0E /* AppDelegate.swift in Sources */, 164 | ); 165 | runOnlyForDeploymentPostprocessing = 0; 166 | }; 167 | /* End PBXSourcesBuildPhase section */ 168 | 169 | /* Begin PBXVariantGroup section */ 170 | 9D2663ED1F15693400EB5B0E /* Main.storyboard */ = { 171 | isa = PBXVariantGroup; 172 | children = ( 173 | 9D2663EE1F15693400EB5B0E /* Base */, 174 | ); 175 | name = Main.storyboard; 176 | sourceTree = ""; 177 | }; 178 | 9D2663F21F15693400EB5B0E /* LaunchScreen.storyboard */ = { 179 | isa = PBXVariantGroup; 180 | children = ( 181 | 9D2663F31F15693400EB5B0E /* Base */, 182 | ); 183 | name = LaunchScreen.storyboard; 184 | sourceTree = ""; 185 | }; 186 | /* End PBXVariantGroup section */ 187 | 188 | /* Begin XCBuildConfiguration section */ 189 | 9D2663F61F15693400EB5B0E /* Debug */ = { 190 | isa = XCBuildConfiguration; 191 | buildSettings = { 192 | ALWAYS_SEARCH_USER_PATHS = NO; 193 | CLANG_ANALYZER_NONNULL = YES; 194 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 195 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 196 | CLANG_CXX_LIBRARY = "libc++"; 197 | CLANG_ENABLE_MODULES = YES; 198 | CLANG_ENABLE_OBJC_ARC = YES; 199 | CLANG_WARN_BOOL_CONVERSION = YES; 200 | CLANG_WARN_CONSTANT_CONVERSION = YES; 201 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 202 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 203 | CLANG_WARN_EMPTY_BODY = YES; 204 | CLANG_WARN_ENUM_CONVERSION = YES; 205 | CLANG_WARN_INFINITE_RECURSION = YES; 206 | CLANG_WARN_INT_CONVERSION = YES; 207 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 208 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 209 | CLANG_WARN_UNREACHABLE_CODE = YES; 210 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 211 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 212 | COPY_PHASE_STRIP = NO; 213 | DEBUG_INFORMATION_FORMAT = dwarf; 214 | ENABLE_STRICT_OBJC_MSGSEND = YES; 215 | ENABLE_TESTABILITY = YES; 216 | GCC_C_LANGUAGE_STANDARD = gnu99; 217 | GCC_DYNAMIC_NO_PIC = NO; 218 | GCC_NO_COMMON_BLOCKS = YES; 219 | GCC_OPTIMIZATION_LEVEL = 0; 220 | GCC_PREPROCESSOR_DEFINITIONS = ( 221 | "DEBUG=1", 222 | "$(inherited)", 223 | ); 224 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 225 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 226 | GCC_WARN_UNDECLARED_SELECTOR = YES; 227 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 228 | GCC_WARN_UNUSED_FUNCTION = YES; 229 | GCC_WARN_UNUSED_VARIABLE = YES; 230 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 231 | MTL_ENABLE_DEBUG_INFO = YES; 232 | ONLY_ACTIVE_ARCH = YES; 233 | SDKROOT = iphoneos; 234 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 235 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 236 | SWIFT_VERSION = 5.0; 237 | TARGETED_DEVICE_FAMILY = "1,2"; 238 | }; 239 | name = Debug; 240 | }; 241 | 9D2663F71F15693400EB5B0E /* Release */ = { 242 | isa = XCBuildConfiguration; 243 | buildSettings = { 244 | ALWAYS_SEARCH_USER_PATHS = NO; 245 | CLANG_ANALYZER_NONNULL = YES; 246 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 247 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 248 | CLANG_CXX_LIBRARY = "libc++"; 249 | CLANG_ENABLE_MODULES = YES; 250 | CLANG_ENABLE_OBJC_ARC = YES; 251 | CLANG_WARN_BOOL_CONVERSION = YES; 252 | CLANG_WARN_CONSTANT_CONVERSION = YES; 253 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 254 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 255 | CLANG_WARN_EMPTY_BODY = YES; 256 | CLANG_WARN_ENUM_CONVERSION = YES; 257 | CLANG_WARN_INFINITE_RECURSION = YES; 258 | CLANG_WARN_INT_CONVERSION = YES; 259 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 260 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 261 | CLANG_WARN_UNREACHABLE_CODE = YES; 262 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 263 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 264 | COPY_PHASE_STRIP = NO; 265 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 266 | ENABLE_NS_ASSERTIONS = NO; 267 | ENABLE_STRICT_OBJC_MSGSEND = YES; 268 | GCC_C_LANGUAGE_STANDARD = gnu99; 269 | GCC_NO_COMMON_BLOCKS = YES; 270 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 271 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 272 | GCC_WARN_UNDECLARED_SELECTOR = YES; 273 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 274 | GCC_WARN_UNUSED_FUNCTION = YES; 275 | GCC_WARN_UNUSED_VARIABLE = YES; 276 | IPHONEOS_DEPLOYMENT_TARGET = 10.3; 277 | MTL_ENABLE_DEBUG_INFO = NO; 278 | SDKROOT = iphoneos; 279 | SWIFT_COMPILATION_MODE = wholemodule; 280 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 281 | SWIFT_VERSION = 5.0; 282 | TARGETED_DEVICE_FAMILY = "1,2"; 283 | VALIDATE_PRODUCT = YES; 284 | }; 285 | name = Release; 286 | }; 287 | 9D2663F91F15693400EB5B0E /* Debug */ = { 288 | isa = XCBuildConfiguration; 289 | buildSettings = { 290 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 291 | DEVELOPMENT_TEAM = 35DWKWL79J; 292 | INFOPLIST_FILE = AZImagePreviewExample/Info.plist; 293 | LD_RUNPATH_SEARCH_PATHS = ( 294 | "$(inherited)", 295 | "@executable_path/Frameworks", 296 | ); 297 | PRODUCT_BUNDLE_IDENTIFIER = net.crofis.AZImagePreviewExample; 298 | PRODUCT_NAME = "$(TARGET_NAME)"; 299 | SWIFT_VERSION = 5.0; 300 | }; 301 | name = Debug; 302 | }; 303 | 9D2663FA1F15693400EB5B0E /* Release */ = { 304 | isa = XCBuildConfiguration; 305 | buildSettings = { 306 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 307 | DEVELOPMENT_TEAM = 35DWKWL79J; 308 | INFOPLIST_FILE = AZImagePreviewExample/Info.plist; 309 | LD_RUNPATH_SEARCH_PATHS = ( 310 | "$(inherited)", 311 | "@executable_path/Frameworks", 312 | ); 313 | PRODUCT_BUNDLE_IDENTIFIER = net.crofis.AZImagePreviewExample; 314 | PRODUCT_NAME = "$(TARGET_NAME)"; 315 | SWIFT_VERSION = 5.0; 316 | }; 317 | name = Release; 318 | }; 319 | /* End XCBuildConfiguration section */ 320 | 321 | /* Begin XCConfigurationList section */ 322 | 9D2663E11F15693400EB5B0E /* Build configuration list for PBXProject "AZImagePreviewExample" */ = { 323 | isa = XCConfigurationList; 324 | buildConfigurations = ( 325 | 9D2663F61F15693400EB5B0E /* Debug */, 326 | 9D2663F71F15693400EB5B0E /* Release */, 327 | ); 328 | defaultConfigurationIsVisible = 0; 329 | defaultConfigurationName = Release; 330 | }; 331 | 9D2663F81F15693400EB5B0E /* Build configuration list for PBXNativeTarget "AZImagePreviewExample" */ = { 332 | isa = XCConfigurationList; 333 | buildConfigurations = ( 334 | 9D2663F91F15693400EB5B0E /* Debug */, 335 | 9D2663FA1F15693400EB5B0E /* Release */, 336 | ); 337 | defaultConfigurationIsVisible = 0; 338 | defaultConfigurationName = Release; 339 | }; 340 | /* End XCConfigurationList section */ 341 | 342 | /* Begin XCRemoteSwiftPackageReference section */ 343 | CF778DA12538335400D71C25 /* XCRemoteSwiftPackageReference "." */ = { 344 | isa = XCRemoteSwiftPackageReference; 345 | repositoryURL = ../; 346 | requirement = { 347 | kind = upToNextMajorVersion; 348 | minimumVersion = 1.2.0; 349 | }; 350 | }; 351 | /* End XCRemoteSwiftPackageReference section */ 352 | 353 | /* Begin XCSwiftPackageProductDependency section */ 354 | CF778DA22538335400D71C25 /* AZImagePreview */ = { 355 | isa = XCSwiftPackageProductDependency; 356 | package = CF778DA12538335400D71C25 /* XCRemoteSwiftPackageReference "." */; 357 | productName = AZImagePreview; 358 | }; 359 | /* End XCSwiftPackageProductDependency section */ 360 | }; 361 | rootObject = 9D2663DE1F15693400EB5B0E /* Project object */; 362 | } 363 | -------------------------------------------------------------------------------- /AZImagePreviewExample/AZImagePreviewExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /AZImagePreviewExample/AZImagePreviewExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /AZImagePreviewExample/AZImagePreviewExample.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /AZImagePreviewExample/AZImagePreviewExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /AZImagePreviewExample/AZImagePreviewExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // AZImagePreviewExample 4 | // 5 | // Created by Antonio Zaitoun on 11/07/2017. 6 | // Copyright © 2017 Antonio Zaitoun. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | private 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 | -------------------------------------------------------------------------------- /AZImagePreviewExample/AZImagePreviewExample/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 | } -------------------------------------------------------------------------------- /AZImagePreviewExample/AZImagePreviewExample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /AZImagePreviewExample/AZImagePreviewExample/Assets.xcassets/ic_file_download.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "ic_file_download.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "ic_file_download_2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "ic_file_download_3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /AZImagePreviewExample/AZImagePreviewExample/Assets.xcassets/ic_file_download.imageset/ic_file_download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minitour/AZImagePreview/2fc7f781532ba2c969129b4831b5fc5c26ab9349/AZImagePreviewExample/AZImagePreviewExample/Assets.xcassets/ic_file_download.imageset/ic_file_download.png -------------------------------------------------------------------------------- /AZImagePreviewExample/AZImagePreviewExample/Assets.xcassets/ic_file_download.imageset/ic_file_download_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minitour/AZImagePreview/2fc7f781532ba2c969129b4831b5fc5c26ab9349/AZImagePreviewExample/AZImagePreviewExample/Assets.xcassets/ic_file_download.imageset/ic_file_download_2x.png -------------------------------------------------------------------------------- /AZImagePreviewExample/AZImagePreviewExample/Assets.xcassets/ic_file_download.imageset/ic_file_download_3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minitour/AZImagePreview/2fc7f781532ba2c969129b4831b5fc5c26ab9349/AZImagePreviewExample/AZImagePreviewExample/Assets.xcassets/ic_file_download.imageset/ic_file_download_3x.png -------------------------------------------------------------------------------- /AZImagePreviewExample/AZImagePreviewExample/Assets.xcassets/image_1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "adam-kool-11868.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /AZImagePreviewExample/AZImagePreviewExample/Assets.xcassets/image_1.imageset/adam-kool-11868.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minitour/AZImagePreview/2fc7f781532ba2c969129b4831b5fc5c26ab9349/AZImagePreviewExample/AZImagePreviewExample/Assets.xcassets/image_1.imageset/adam-kool-11868.jpg -------------------------------------------------------------------------------- /AZImagePreviewExample/AZImagePreviewExample/Assets.xcassets/image_2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "adam-wilson-306570.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /AZImagePreviewExample/AZImagePreviewExample/Assets.xcassets/image_2.imageset/adam-wilson-306570.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minitour/AZImagePreview/2fc7f781532ba2c969129b4831b5fc5c26ab9349/AZImagePreviewExample/AZImagePreviewExample/Assets.xcassets/image_2.imageset/adam-wilson-306570.jpg -------------------------------------------------------------------------------- /AZImagePreviewExample/AZImagePreviewExample/Assets.xcassets/image_3.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "hamburger-arts-260481.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /AZImagePreviewExample/AZImagePreviewExample/Assets.xcassets/image_3.imageset/hamburger-arts-260481.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minitour/AZImagePreview/2fc7f781532ba2c969129b4831b5fc5c26ab9349/AZImagePreviewExample/AZImagePreviewExample/Assets.xcassets/image_3.imageset/hamburger-arts-260481.jpg -------------------------------------------------------------------------------- /AZImagePreviewExample/AZImagePreviewExample/Assets.xcassets/image_4.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "matthew-henry-213827.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /AZImagePreviewExample/AZImagePreviewExample/Assets.xcassets/image_4.imageset/matthew-henry-213827.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minitour/AZImagePreview/2fc7f781532ba2c969129b4831b5fc5c26ab9349/AZImagePreviewExample/AZImagePreviewExample/Assets.xcassets/image_4.imageset/matthew-henry-213827.jpg -------------------------------------------------------------------------------- /AZImagePreviewExample/AZImagePreviewExample/Assets.xcassets/image_5.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "andrew-neel-308138.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /AZImagePreviewExample/AZImagePreviewExample/Assets.xcassets/image_5.imageset/andrew-neel-308138.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minitour/AZImagePreview/2fc7f781532ba2c969129b4831b5fc5c26ab9349/AZImagePreviewExample/AZImagePreviewExample/Assets.xcassets/image_5.imageset/andrew-neel-308138.jpg -------------------------------------------------------------------------------- /AZImagePreviewExample/AZImagePreviewExample/Assets.xcassets/image_6.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "anton-darius-sollers-280400.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /AZImagePreviewExample/AZImagePreviewExample/Assets.xcassets/image_6.imageset/anton-darius-sollers-280400.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minitour/AZImagePreview/2fc7f781532ba2c969129b4831b5fc5c26ab9349/AZImagePreviewExample/AZImagePreviewExample/Assets.xcassets/image_6.imageset/anton-darius-sollers-280400.jpg -------------------------------------------------------------------------------- /AZImagePreviewExample/AZImagePreviewExample/Assets.xcassets/image_7.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "aleksandr-ledogorov-212542.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /AZImagePreviewExample/AZImagePreviewExample/Assets.xcassets/image_7.imageset/aleksandr-ledogorov-212542.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minitour/AZImagePreview/2fc7f781532ba2c969129b4831b5fc5c26ab9349/AZImagePreviewExample/AZImagePreviewExample/Assets.xcassets/image_7.imageset/aleksandr-ledogorov-212542.jpg -------------------------------------------------------------------------------- /AZImagePreviewExample/AZImagePreviewExample/Assets.xcassets/image_8.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "andrew-pons-57133.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /AZImagePreviewExample/AZImagePreviewExample/Assets.xcassets/image_8.imageset/andrew-pons-57133.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minitour/AZImagePreview/2fc7f781532ba2c969129b4831b5fc5c26ab9349/AZImagePreviewExample/AZImagePreviewExample/Assets.xcassets/image_8.imageset/andrew-pons-57133.jpg -------------------------------------------------------------------------------- /AZImagePreviewExample/AZImagePreviewExample/Assets.xcassets/image_9.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "anthony-delanoix-152872.jpg", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /AZImagePreviewExample/AZImagePreviewExample/Assets.xcassets/image_9.imageset/anthony-delanoix-152872.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minitour/AZImagePreview/2fc7f781532ba2c969129b4831b5fc5c26ab9349/AZImagePreviewExample/AZImagePreviewExample/Assets.xcassets/image_9.imageset/anthony-delanoix-152872.jpg -------------------------------------------------------------------------------- /AZImagePreviewExample/AZImagePreviewExample/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 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /AZImagePreviewExample/AZImagePreviewExample/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 | 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 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /AZImagePreviewExample/AZImagePreviewExample/CircleImageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CircularImageView.swift 3 | // AZImagePreviewExample 4 | // 5 | // Created by Antonio Zaitoun on 12/07/2017. 6 | // Copyright © 2017 Antonio Zaitoun. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | 12 | @IBDesignable 13 | class CircleImageView: UIImageView { 14 | 15 | override func layoutSubviews() { 16 | self.layer.cornerRadius = self.frame.height / 2 17 | self.layer.masksToBounds = true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /AZImagePreviewExample/AZImagePreviewExample/CollectionViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionViewController.swift 3 | // AZImagePreviewExample 4 | // 5 | // Created by Antonio Zaitoun on 12/07/2017. 6 | // Copyright © 2017 Antonio Zaitoun. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AZImagePreview 11 | 12 | public class CollectionViewController: UICollectionViewController{ 13 | 14 | lazy var images: [UIImage] = [#imageLiteral(resourceName: "image_1"), 15 | #imageLiteral(resourceName: "image_2"), 16 | #imageLiteral(resourceName: "image_3"), 17 | #imageLiteral(resourceName: "image_4"), 18 | #imageLiteral(resourceName: "image_5"), 19 | #imageLiteral(resourceName: "image_6"), 20 | #imageLiteral(resourceName: "image_7"), 21 | #imageLiteral(resourceName: "image_8"), 22 | #imageLiteral(resourceName: "image_9"), 23 | #imageLiteral(resourceName: "image_1"), 24 | #imageLiteral(resourceName: "image_2"), 25 | #imageLiteral(resourceName: "image_3"), 26 | #imageLiteral(resourceName: "image_4"), 27 | #imageLiteral(resourceName: "image_5"), 28 | #imageLiteral(resourceName: "image_6"), 29 | #imageLiteral(resourceName: "image_7"), 30 | #imageLiteral(resourceName: "image_8"), 31 | #imageLiteral(resourceName: "image_9"), 32 | #imageLiteral(resourceName: "image_1"), 33 | #imageLiteral(resourceName: "image_2"), 34 | #imageLiteral(resourceName: "image_3"), 35 | #imageLiteral(resourceName: "image_4"), 36 | #imageLiteral(resourceName: "image_5"), 37 | #imageLiteral(resourceName: "image_6"), 38 | #imageLiteral(resourceName: "image_7"), 39 | #imageLiteral(resourceName: "image_8"), 40 | #imageLiteral(resourceName: "image_9"), 41 | #imageLiteral(resourceName: "image_1"), 42 | #imageLiteral(resourceName: "image_2"), 43 | #imageLiteral(resourceName: "image_3"), 44 | #imageLiteral(resourceName: "image_4"), 45 | #imageLiteral(resourceName: "image_5"), 46 | #imageLiteral(resourceName: "image_6"), 47 | #imageLiteral(resourceName: "image_7"), 48 | #imageLiteral(resourceName: "image_8"), 49 | #imageLiteral(resourceName: "image_9") 50 | ] 51 | 52 | public override func viewDidLoad() { 53 | super.viewDidLoad() 54 | 55 | } 56 | 57 | public override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 58 | if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCollectionCell", for: indexPath) as? CustomCollectionCell{ 59 | 60 | 61 | 62 | cell.image.image = images[indexPath.item] 63 | if cell.image.delegate == nil{ 64 | cell.image.delegate = self 65 | } 66 | return cell 67 | } 68 | return UICollectionViewCell() 69 | } 70 | 71 | 72 | public override func numberOfSections(in collectionView: UICollectionView) -> Int { 73 | return 1 74 | } 75 | 76 | public override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 77 | return images.count 78 | } 79 | 80 | } 81 | 82 | extension CollectionViewController: AZPreviewImageViewDelegate{ 83 | public func previewImageViewInRespectTo(_ previewImageView: UIImageView) -> UIView? { 84 | return view 85 | } 86 | 87 | public func previewImageView(_ previewImageView: UIImageView, requestImagePreviewWithPreseneter presenter: AZImagePresenterViewController) { 88 | //presenter.isStatusBarHidden = true 89 | presenter.statusBarStyle = .default 90 | presenter.backgroundColor = .white 91 | presenter.dismissDirection = .both 92 | presenter.animationDuration = 0.2 93 | presenter.minimumScale = 0.5 94 | presenter.tintColor = UIButton().tintColor 95 | let nav = presenter.embedInNavigation() 96 | nav.isToolbarHidden = false 97 | 98 | presenter.addAction(AZPresenterAction(icon: 99 | #imageLiteral(resourceName: "ic_file_download")){ (presenter,imageView) in 100 | 101 | presenter.addAction(AZPresenterAction(icon: #imageLiteral(resourceName: "ic_file_download")) { (presenter, image) in 102 | }) 103 | 104 | }) 105 | 106 | self.present(nav, animated: false, completion: nil) 107 | } 108 | } 109 | 110 | extension CollectionViewController: UICollectionViewDelegateFlowLayout{ 111 | public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { 112 | let width = collectionView.frame.size.width / 4 113 | return CGSize(width: width, height: width) 114 | } 115 | 116 | public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { 117 | return 0 118 | } 119 | 120 | } 121 | 122 | public class CustomCollectionCell: UICollectionViewCell{ 123 | 124 | @IBOutlet weak var image: CircleImageView! 125 | } 126 | -------------------------------------------------------------------------------- /AZImagePreviewExample/AZImagePreviewExample/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 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /AZImagePreviewExample/AZImagePreviewExample/TableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableViewController.swift 3 | // AZImagePreviewExample 4 | // 5 | // Created by Antonio Zaitoun on 11/07/2017. 6 | // Copyright © 2017 Antonio Zaitoun. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AZImagePreview 11 | 12 | class TableViewController: UITableViewController{ 13 | 14 | 15 | lazy var data: [UIImage] = [#imageLiteral(resourceName: "image_1"), 16 | #imageLiteral(resourceName: "image_2"), 17 | #imageLiteral(resourceName: "image_3"), 18 | #imageLiteral(resourceName: "image_4"), 19 | #imageLiteral(resourceName: "image_5"), 20 | #imageLiteral(resourceName: "image_6"), 21 | #imageLiteral(resourceName: "image_7"), 22 | #imageLiteral(resourceName: "image_8"), 23 | #imageLiteral(resourceName: "image_9")] 24 | 25 | 26 | override func viewDidLoad() { 27 | super.viewDidLoad() 28 | tableView?.register(ImageCell.self, forCellReuseIdentifier: "cell") 29 | } 30 | 31 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 32 | if let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as? ImageCell { 33 | cell.imageView?.image = data[indexPath.row] 34 | cell.imageView?.delegate = self 35 | return cell 36 | } 37 | return UITableViewCell() 38 | } 39 | 40 | override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 41 | 42 | let imageSize = data[indexPath.row].size 43 | 44 | return view.frame.width * (imageSize.height/imageSize.width) 45 | } 46 | 47 | override func numberOfSections(in tableView: UITableView) -> Int { 48 | return 1 49 | } 50 | 51 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 52 | return data.count 53 | } 54 | } 55 | 56 | extension TableViewController: AZPreviewImageViewDelegate{ 57 | func previewImageViewInRespectTo(_ previewImageView: UIImageView) -> UIView? { 58 | return navigationController?.view 59 | } 60 | 61 | func previewImageView(_ previewImageView: UIImageView, requestImagePreviewWithPreseneter presenter: AZImagePresenterViewController) { 62 | present(presenter, animated: false, completion: nil) 63 | } 64 | } 65 | 66 | class ImageCell: UITableViewCell{ 67 | 68 | private var customImage: UIImageView! 69 | 70 | override var imageView: UIImageView?{ 71 | return customImage 72 | } 73 | 74 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 75 | super.init(style: style, reuseIdentifier: reuseIdentifier) 76 | customImage = UIImageView() 77 | customImage.contentMode = .scaleToFill 78 | addSubview(customImage) 79 | customImage.translatesAutoresizingMaskIntoConstraints = false 80 | customImage.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 1.0).isActive = true 81 | customImage.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 1.0).isActive = true 82 | customImage.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true 83 | customImage.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true 84 | 85 | } 86 | required init?(coder aDecoder: NSCoder) { 87 | fatalError("init(coder:) has not been implemented") 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /AZImagePreviewExample/AZImagePreviewExample/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // AZImagePreviewExample 4 | // 5 | // Created by Antonio Zaitoun on 11/07/2017. 6 | // Copyright © 2017 Antonio Zaitoun. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import AZImagePreview 11 | 12 | class ViewController: UIViewController { 13 | 14 | @IBOutlet weak var imageView1: UIImageView! 15 | @IBOutlet weak var imageView2: UIImageView! 16 | 17 | @IBAction func showTableView(_ sender: UIBarButtonItem) { 18 | let controller = TableViewController() 19 | navigationController?.pushViewController(controller, animated: true) 20 | } 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | imageView1.delegate = self 24 | imageView2.delegate = self 25 | 26 | 27 | 28 | // Do any additional setup after loading the view, typically from a nib. 29 | } 30 | 31 | override func didReceiveMemoryWarning() { 32 | super.didReceiveMemoryWarning() 33 | // Dispose of any resources that can be recreated. 34 | } 35 | 36 | override func viewDidAppear(_ animated: Bool) { 37 | super.viewDidAppear(animated) 38 | imageView2.layer.cornerRadius = imageView2.frame.height/2 39 | } 40 | 41 | 42 | } 43 | 44 | extension ViewController: AZPreviewImageViewDelegate{ 45 | func previewImageViewInRespectTo(_ previewImageView: UIImageView) -> UIView? { 46 | return view 47 | } 48 | 49 | func previewImageView(_ previewImageView: UIImageView, requestImagePreviewWithPreseneter presenter: AZImagePresenterViewController) { 50 | present(presenter, animated: false, completion: nil) 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Antonio Zaitoun 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 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "AZImagePreview", 7 | products: [ 8 | .library( 9 | name: "AZImagePreview", 10 | targets: ["AZImagePreview"]), 11 | ], 12 | targets: [ 13 | .target( 14 | name: "AZImagePreview", 15 | dependencies: []), 16 | ] 17 | ) 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AZImagePreview 2 | iOS Framework that makes it easy to preview images on any UIImageView. 3 | 4 | ## Screenshots 5 | 6 | 7 | 8 | ## Installation: 9 | 10 | ### Cocoa Pods: 11 | 12 | ```bash 13 | pod 'AZImagePreview' 14 | ``` 15 | 16 | ### Swift Package Manager 17 | 18 | You can use [The Swift Package Manager](https://swift.org/package-manager) to install `AZImagePreview` by adding the proper description to your `Package.swift` file: 19 | 20 | ```swift 21 | // swift-tools-version:4.0 22 | import PackageDescription 23 | 24 | let package = Package( 25 | name: "YOUR_PROJECT_NAME", 26 | dependencies: [ 27 | .package(url: "https://github.com/Minitour/AZImagePreview.git", from: "1.2.1"), 28 | ] 29 | ) 30 | ``` 31 | Then run `swift build` whenever you get prepared. 32 | 33 | ### Manual: 34 | 35 | Simply drag and drop the ```Sources``` folder to your project. 36 | 37 | 38 | ### Conform to the AZPreviewImageViewDelegate protocol: 39 | 40 | ```swift 41 | 42 | extension ViewController: AZPreviewImageViewDelegate{ 43 | func previewImageViewInRespectTo(_ previewImageView: UIImageView) -> UIView? { 44 | //return self.view or self.navigationController?.view (if you are using a navigation controller. 45 | return view 46 | } 47 | 48 | func previewImageView(_ previewImageView: UIImageView, requestImagePreviewWithPreseneter presenter: AZImagePresenterViewController) { 49 | present(presenter, animated: false, completion: nil) 50 | } 51 | } 52 | 53 | ``` 54 | 55 | ### Set the delegate on the UIImageView: 56 | 57 | ```swift 58 | 59 | @IBOutlet weak var imageView: UIImageView! 60 | 61 | override func viewDidLoad(){ 62 | super.viewDidLoad() 63 | 64 | imageView.delegate = self 65 | } 66 | 67 | ``` 68 | -------------------------------------------------------------------------------- /Screenshots/sc1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minitour/AZImagePreview/2fc7f781532ba2c969129b4831b5fc5c26ab9349/Screenshots/sc1.gif -------------------------------------------------------------------------------- /Screenshots/sc2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minitour/AZImagePreview/2fc7f781532ba2c969129b4831b5fc5c26ab9349/Screenshots/sc2.gif -------------------------------------------------------------------------------- /Sources/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Minitour/AZImagePreview/2fc7f781532ba2c969129b4831b5fc5c26ab9349/Sources/.DS_Store -------------------------------------------------------------------------------- /Sources/AZImagePreview/AZImagePresenterViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreviewImageView.swift 3 | // 4 | // 5 | // Created by Antonio Zaitoun on 03/07/2017. 6 | // Copyright © 2017 Antonio Zaitoun. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | 12 | public typealias AZPresenterHandler = (AZImagePresenterViewController,UIImageView)->Void 13 | 14 | public struct AZPresenterAction{ 15 | public var icon: UIImage 16 | public var handler: AZPresenterHandler 17 | 18 | public init(icon: UIImage, handler: @escaping AZPresenterHandler) { 19 | self.icon = icon 20 | self.handler = handler 21 | } 22 | } 23 | 24 | // MARK: - AZPreviewDismissDirection 25 | 26 | public enum AZPreviewDismissDirection{ 27 | case top 28 | case bottom 29 | case both 30 | case none 31 | } 32 | 33 | // MARK: - AZImagePresenterViewController 34 | 35 | open class AZImagePresenterViewController: UIViewController{ 36 | 37 | open class func embedInNavigation(_ controller: AZImagePresenterViewController)->UINavigationController{ 38 | let navigationController = FixedNavigationController(rootViewController: controller) 39 | navigationController.modalPresentationStyle = .overFullScreen 40 | navigationController.modalTransitionStyle = .crossDissolve 41 | navigationController.navigationBar.setBackgroundImage(UIImage(), for:UIBarMetrics.default) 42 | navigationController.navigationBar.isTranslucent = true 43 | navigationController.toolbar.isTranslucent = true 44 | navigationController.toolbar.setShadowImage(UIImage(), forToolbarPosition: .any) 45 | navigationController.toolbar.setBackgroundImage(UIImage(), forToolbarPosition: .any, barMetrics: .default) 46 | navigationController.navigationBar.shadowImage = UIImage() 47 | 48 | return navigationController 49 | } 50 | 51 | open func embedInNavigation()->UINavigationController{ 52 | let navigationController = AZImagePresenterViewController.embedInNavigation(self) 53 | navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .stop, target: self, action: #selector(done(_:))) 54 | navigationController.navigationBar.tintColor = tintColor 55 | return navigationController 56 | } 57 | 58 | fileprivate var scrollView: PannableScrollView! 59 | 60 | 61 | // MARK: - private property 62 | 63 | /// Reference to the original image view 64 | open var originalImage: UIImageView? 65 | 66 | /// The delegate that was set on the image 67 | open weak var delegate: AZPreviewImageViewDelegate? 68 | 69 | /// The image that we drag around 70 | fileprivate var imageView: TrackableImage? 71 | 72 | /// The center point of the original image. This property is a computed property and it needs the delegate in order to compute. 73 | fileprivate var center: CGPoint?{ 74 | if let originalImage = originalImage,let superView = delegate?.previewImageViewInRespectTo(originalImage){ 75 | return superView.convert(originalImage.center, to: nil) 76 | } 77 | return nil 78 | } 79 | 80 | /// The rect (frame) of the original image in respect to the view that was provided in the delegate. 81 | fileprivate var rect: CGRect?{ 82 | if let originalImage = originalImage,let superView = delegate?.previewImageViewInRespectTo(originalImage){ 83 | return originalImage.superview!.convert(originalImage.frame, to: superView) 84 | } 85 | return nil 86 | } 87 | 88 | fileprivate var isZoomedIn: Bool { 89 | return self.scrollView.zoomScale > self.scrollView.minimumZoomScale 90 | } 91 | 92 | fileprivate var originalContentMode: UIView.ContentMode = .scaleAspectFit 93 | 94 | fileprivate var actions: [AZPresenterAction?] = [] 95 | 96 | /// The direction in which the image can be dismissed uppong drag 97 | open var dismissDirection: AZPreviewDismissDirection = .both 98 | 99 | // The background color 100 | open var backgroundColor: UIColor = .white 101 | 102 | // The animation duration 103 | open var animationDuration: TimeInterval = 0.2 104 | 105 | open var scaleOnDrag: Bool = true 106 | 107 | /// 0 or lower means scale to original size. 108 | open var minimumScale: CGFloat = 0.0 109 | 110 | open var tintColor: UIColor = .black { 111 | didSet{ 112 | navigationController?.navigationBar.tintColor = tintColor 113 | navigationController?.toolbar.tintColor = tintColor 114 | } 115 | } 116 | 117 | /// hide/show status bar 118 | open var isStatusBarHidden = false{ 119 | didSet{ 120 | setNeedsStatusBarAppearanceUpdate() 121 | } 122 | } 123 | 124 | /// change status bar style 125 | open var statusBarStyle: UIStatusBarStyle = .default{ 126 | didSet{ 127 | setNeedsStatusBarAppearanceUpdate() 128 | } 129 | } 130 | 131 | 132 | // MARK: - UIViewController 133 | 134 | override open var preferredStatusBarUpdateAnimation: UIStatusBarAnimation{ 135 | return .fade 136 | } 137 | 138 | override open var prefersStatusBarHidden: Bool{ 139 | return isStatusBarHidden 140 | } 141 | 142 | override open var preferredStatusBarStyle: UIStatusBarStyle{ 143 | return statusBarStyle 144 | } 145 | 146 | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { 147 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 148 | setup() 149 | } 150 | 151 | required public init?(coder aDecoder: NSCoder) { 152 | fatalError("init(coder:) has not been implemented") 153 | } 154 | 155 | override open func loadView() { 156 | super.loadView() 157 | scrollView = PannableScrollView() 158 | imageView = TrackableImage() 159 | view.addSubview(scrollView) 160 | 161 | view.addSubview(imageView!) 162 | scrollView.delegate = self 163 | scrollView.translatesAutoresizingMaskIntoConstraints = false 164 | scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true 165 | scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true 166 | scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true 167 | scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true 168 | scrollView.maximumZoomScale = 4.0 169 | 170 | 171 | } 172 | 173 | fileprivate var isNavigationBarHidden = false 174 | 175 | fileprivate var isToolBarHidden = false 176 | 177 | 178 | override open func viewDidLoad() { 179 | super.viewDidLoad() 180 | isNavigationBarHidden = navigationController?.isNavigationBarHidden ?? false 181 | isToolBarHidden = navigationController?.isToolbarHidden ?? false 182 | navigationController?.isNavigationBarHidden = true 183 | navigationController?.isToolbarHidden = true 184 | 185 | let doubleTapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:))) 186 | doubleTapGesture.numberOfTapsRequired = 2 187 | imageView?.addGestureRecognizer(doubleTapGesture) 188 | imageView?.image = originalImage?.image 189 | imageView?.contentMode = .scaleAspectFit 190 | imageView?.isUserInteractionEnabled = true 191 | } 192 | 193 | open func addAction(_ action: AZPresenterAction){ 194 | actions.append(action) 195 | 196 | setupActions() 197 | } 198 | 199 | open func removeAction(at index: Int){ 200 | if index >= actions.count{ return } 201 | 202 | actions.remove(at: index) 203 | 204 | setupActions() 205 | } 206 | 207 | func setupActions(){ 208 | 209 | if let navigationController = self.navigationController, let toolBar = navigationController.toolbar{ 210 | toolBar.items?.removeAll() 211 | 212 | var items = [UIBarButtonItem]() 213 | 214 | var index = 0 215 | for action in actions{ 216 | let item = UIBarButtonItem(image: action?.icon, 217 | landscapeImagePhone: nil, 218 | style: .plain, 219 | target: self, 220 | action: #selector(didSelectToolBarItem(sender:))) 221 | item.tag = index 222 | index += 1 223 | items.append(item) 224 | } 225 | 226 | if items.count == 0 { 227 | navigationController.setToolbarHidden(true, animated: true) 228 | }else{ 229 | setToolbarItems(items, animated: false) 230 | if navigationController.isToolbarHidden { 231 | navigationController.setToolbarHidden(false, animated: true) 232 | } 233 | } 234 | } 235 | } 236 | 237 | 238 | 239 | override open func viewDidAppear(_ animated: Bool) { 240 | super.viewDidAppear(animated) 241 | presentView() 242 | setupActions() 243 | } 244 | 245 | // MARK: - Helpers 246 | 247 | fileprivate var isPortrait: Bool{ 248 | return view.frame.height > view.frame.width 249 | } 250 | 251 | fileprivate(set) open var isDragging: Bool = false{ 252 | didSet{ 253 | if let item = navigationItem.leftBarButtonItem{ 254 | item.isEnabled = !isDragging 255 | } 256 | } 257 | } 258 | 259 | fileprivate var scale: CGFloat{ 260 | let ratio = originalImage!.frame.width / view.frame.width 261 | return ratio >= 1.0 ? 1.0 : ratio 262 | } 263 | 264 | 265 | fileprivate func setup(){ 266 | modalTransitionStyle = .crossDissolve 267 | modalPresentationStyle = .overFullScreen 268 | modalPresentationCapturesStatusBarAppearance = true 269 | } 270 | 271 | fileprivate func prepareForDismiss(){ 272 | originalImage?.alpha = 1.0 273 | dismiss(animated: false, completion: nil) 274 | } 275 | 276 | @objc internal func handleTap(_ sender: UITapGestureRecognizer) { 277 | UIView.animate(withDuration: animationDuration) { 278 | if self.isZoomedIn { 279 | // zoom out 280 | self.scrollView.zoomScale = self.scrollView.minimumZoomScale 281 | }else { 282 | // zoom in 283 | self.scrollView.zoomScale = ( min(self.scrollView.minimumZoomScale * 4.0,self.scrollView.maximumZoomScale / 2.0) ) 284 | } 285 | } 286 | } 287 | 288 | 289 | @objc internal func done(_ sender: UIBarButtonItem){ 290 | if let imageView = imageView, !isDragging{ 291 | var safePoint = imageView.center 292 | safePoint.y = view.frame.maxY + imageView.bounds.midY 293 | dismissDirection(imageView,scale: 1.0,finalPoint: safePoint) 294 | } 295 | } 296 | 297 | @objc internal func didSelectToolBarItem(sender: UIBarButtonItem){ 298 | actions[sender.tag]?.handler(self,originalImage ?? imageView!) 299 | } 300 | 301 | fileprivate func presentView(){ 302 | 303 | if let navigationController = navigationController{ 304 | 305 | if !isNavigationBarHidden{ 306 | navigationController.setNavigationBarHidden(false, animated: true) 307 | } 308 | 309 | if !isToolBarHidden { 310 | navigationController.setToolbarHidden(false, animated: true) 311 | } 312 | 313 | } 314 | 315 | //make position of uiimageview same as the original one (apply rect) 316 | if let rect = rect{ 317 | imageView?.frame = rect 318 | } 319 | 320 | let isRounded = true//self.isRounded 321 | 322 | if isRounded { 323 | imageView!.layer.cornerRadius = originalImage!.layer.cornerRadius//imageView!.frame.height / 2 324 | imageView?.layer.masksToBounds = true 325 | } 326 | 327 | switch originalImage!.contentMode{ 328 | case .scaleAspectFit, 329 | .scaleAspectFill, 330 | .scaleToFill: 331 | originalContentMode = originalImage!.contentMode 332 | default: 333 | originalContentMode = .scaleAspectFit 334 | } 335 | 336 | imageView?.contentMode = originalContentMode 337 | 338 | //hide make original image hidden 339 | originalImage?.alpha = 0.0 340 | 341 | //animate to center 342 | view.backgroundColor = .clear 343 | 344 | if isRounded{ 345 | 346 | self.imageView! 347 | .addCornerRadiusAnimation(from: imageView!.cornerRadius, 348 | to: 0, duration: animationDuration) 349 | } 350 | 351 | UIView.animate(withDuration: animationDuration, animations: { [weak self] in 352 | if let this = self{ 353 | let width = this.imageView?.image?.size.width 354 | let height = this.imageView?.image?.size.height 355 | 356 | let scale = width!/height! 357 | let sWidth: CGFloat 358 | let sHeight: CGFloat 359 | 360 | if this.isPortrait{ 361 | sWidth = this.view.frame.width 362 | sHeight = sWidth/scale 363 | }else{ 364 | sHeight = this.view.frame.height 365 | sWidth = sHeight * scale 366 | } 367 | 368 | 369 | this.imageView?.frame = CGRect(x: 0, y: 0, width: sWidth, height: sHeight) 370 | this.imageView?.center = this.view.center 371 | this.view.backgroundColor = this.backgroundColor 372 | } 373 | }){ (bool) in 374 | self.imageView?.contentMode = .scaleAspectFit 375 | self.imageView?.frame = self.view.frame 376 | //self.imageView?.autoresizingMask = [.flexibleWidth,.flexibleHeight] 377 | self.scrollView.view = self.imageView 378 | self.scrollView.setZoomScale() 379 | 380 | } 381 | } 382 | 383 | fileprivate func hideBars(hide: Bool){ 384 | self.navigationController?.setNavigationBarHidden(hide, animated: true) 385 | self.navigationController?.setToolbarHidden(hide, animated: true) 386 | } 387 | 388 | fileprivate func dismissDirection(_ baseView: TrackableImage , scale: CGFloat, finalPoint: CGPoint){ 389 | 390 | hideBars(hide: true) 391 | 392 | //here we are resizing the trackable image in order to make the animation smooth 393 | baseView.transform = .identity 394 | //get the width and height of the image 395 | let width = baseView.image?.size.width 396 | let height = baseView.image?.size.height 397 | 398 | //calculate the aspect ratio of the image 399 | let ratio: CGFloat = width!/height! 400 | 401 | //calculate the new size 402 | let sWidth: CGFloat 403 | let sHeight: CGFloat 404 | 405 | if self.isPortrait{ 406 | sWidth = self.view.frame.width 407 | sHeight = sWidth/ratio 408 | }else{ 409 | sHeight = self.view.frame.height 410 | sWidth = sHeight * ratio 411 | } 412 | 413 | //calculate the new center 414 | let newCenter = baseView.center//CGPoint(x: baseView.frame.midX * ratio, y: baseView.frame.midY * ratio) 415 | 416 | //update the image 417 | baseView.frame.size.height = sHeight 418 | baseView.frame.size.height = sWidth 419 | baseView.center = newCenter 420 | baseView.transform = CGAffineTransform(scaleX: scale, y: scale) 421 | 422 | //change the content mode to the original content mode 423 | baseView.contentMode = self.originalContentMode 424 | 425 | baseView.addCornerRadiusAnimation(from: baseView.cornerRadius, to: originalImage!.cornerRadius * (1/scale), duration: animationDuration) 426 | 427 | //animate 428 | UIView.animate(withDuration: animationDuration, animations: { () -> Void in 429 | 430 | //animate the background color to clear 431 | self.view.backgroundColor = .clear 432 | 433 | guard let center = self.center, let rect = self.rect else { 434 | baseView.center = finalPoint 435 | return 436 | } 437 | 438 | //animate rect to original image center and same rect size 439 | baseView.center = center 440 | baseView.frame = rect 441 | 442 | 443 | 444 | 445 | }, completion: { (complete) -> Void in 446 | self.prepareForDismiss() 447 | }) 448 | } 449 | 450 | 451 | // MARK: - TrackableImage class 452 | 453 | fileprivate class TrackableImage: UIImageView{ 454 | 455 | var lastLocation = CGPoint(x: 0, y: 0) 456 | 457 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 458 | lastLocation = self.center 459 | super.touchesBegan(touches, with: event) 460 | } 461 | } 462 | 463 | } 464 | 465 | extension AZImagePresenterViewController: UIScrollViewDelegate { 466 | 467 | public func viewForZooming(in scrollView: UIScrollView) -> UIView? { 468 | return self.scrollView.view 469 | } 470 | 471 | public func scrollViewDidZoom(_ scrollView: UIScrollView) { 472 | if let view = self.scrollView.view { 473 | let viewSize = view.frame.size 474 | let scrollViewSize = scrollView.bounds.size 475 | let verticalInset = viewSize.height < scrollViewSize.height ? (scrollViewSize.height - viewSize.height) / 2 : 0 476 | let horizontalInset = viewSize.width < scrollViewSize.width ? (scrollViewSize.width - viewSize.width) / 2 : 0 477 | scrollView.contentInset = UIEdgeInsets(top: verticalInset, left: horizontalInset, bottom: verticalInset, right: horizontalInset) 478 | } 479 | } 480 | 481 | public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { 482 | 483 | if isZoomedIn { 484 | return 485 | } 486 | 487 | let xV = velocity.x 488 | let yV = velocity.y 489 | 490 | if yV > 0 { 491 | if dismissDirection == .bottom || dismissDirection == .none { 492 | return 493 | } 494 | }else { 495 | if dismissDirection == .top || dismissDirection == .none { 496 | return 497 | } 498 | } 499 | 500 | if max(abs(xV),abs(yV)) > 1.0 { 501 | self.view.addSubview(self.imageView!) 502 | self.imageView?.contentMode = .scaleAspectFit 503 | self.imageView?.frame = self.view.frame 504 | self.imageView?.autoresizingMask = [.flexibleWidth,.flexibleHeight] 505 | dismissDirection(self.imageView!, scale: 1.0, finalPoint: (self.imageView!.superview?.center)!) 506 | } 507 | } 508 | } 509 | 510 | // MARK: - Private class helper, used to add stored extension properties. 511 | 512 | fileprivate final class ObjectAssociation { 513 | 514 | private let policy: objc_AssociationPolicy 515 | 516 | /// - Parameter policy: An association policy that will be used when linking objects. 517 | public init(policy: objc_AssociationPolicy = .OBJC_ASSOCIATION_RETAIN_NONATOMIC) { 518 | 519 | self.policy = policy 520 | } 521 | 522 | /// Accesses associated object. 523 | /// - Parameter index: An object whose associated object is to be accessed. 524 | public subscript(index: AnyObject) -> T? { 525 | 526 | get { return objc_getAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque()) as! T? } 527 | set { objc_setAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque(), newValue, policy) } 528 | } 529 | } 530 | 531 | fileprivate class FixedNavigationController: UINavigationController{ 532 | override var childForStatusBarStyle: UIViewController?{ 533 | return viewControllers.first 534 | } 535 | } 536 | 537 | fileprivate extension UIView 538 | { 539 | func addCornerRadiusAnimation(from: CGFloat, to: CGFloat, duration: CFTimeInterval) 540 | { 541 | let animation = CABasicAnimation(keyPath:"cornerRadius") 542 | animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) 543 | animation.fromValue = from 544 | animation.toValue = to 545 | animation.duration = duration 546 | self.layer.add(animation, forKey: "cornerRadius") 547 | self.layer.cornerRadius = to 548 | } 549 | 550 | var cornerRadius: CGFloat{ 551 | return layer.cornerRadius 552 | } 553 | } 554 | 555 | fileprivate extension CGSize{ 556 | var area: CGFloat{ 557 | return width * height 558 | } 559 | 560 | var isSquare: Bool{ 561 | return width == height 562 | } 563 | } 564 | 565 | 566 | public extension UIImageView{ 567 | 568 | private static let association = ObjectAssociation(policy: .OBJC_ASSOCIATION_ASSIGN) 569 | 570 | var delegate: AZPreviewImageViewDelegate? { 571 | 572 | get { 573 | return UIImageView.association[self] } 574 | set { 575 | UIImageView.association[self] = newValue 576 | setup() 577 | } 578 | } 579 | 580 | private func setup(){ 581 | isUserInteractionEnabled = true 582 | } 583 | 584 | private func preparePresenter()->AZImagePresenterViewController{ 585 | let presenter = AZImagePresenterViewController() 586 | presenter.originalImage = self 587 | presenter.delegate = delegate 588 | return presenter 589 | } 590 | 591 | 592 | override func touchesEnded(_ touches: Set, with event: UIEvent?) { 593 | super.touchesEnded(touches, with: event) 594 | let tap:UITouch = touches.first! 595 | let point = tap.location(in: self) 596 | if self.bounds.contains(point) && image != nil{ 597 | //touch-up-inside 598 | delegate?.previewImageView(self, requestImagePreviewWithPreseneter: preparePresenter()) 599 | } 600 | } 601 | } 602 | 603 | 604 | 605 | 606 | -------------------------------------------------------------------------------- /Sources/AZImagePreview/AZPreviewImageViewDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AZPreviewImageViewDelegate.swift 3 | // AZImagePreviewExample 4 | // 5 | // Created by Antonio Zaitoun on 11/07/2017. 6 | // Copyright © 2017 Antonio Zaitoun. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | // MARK: - AZPreviewImageViewDelegate 12 | 13 | public protocol AZPreviewImageViewDelegate: class{ 14 | 15 | func previewImageView(_ previewImageView: UIImageView,requestImagePreviewWithPreseneter presenter: AZImagePresenterViewController) 16 | 17 | func previewImageViewInRespectTo(_ previewImageView: UIImageView)->UIView? 18 | 19 | } 20 | -------------------------------------------------------------------------------- /Sources/AZImagePreview/PannableScrollView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PannableScrollView.swift 3 | // AZImagePreviewExample 4 | // 5 | // Created by Antonio Zaitoun on 12/10/2018. 6 | // Copyright © 2018 Antonio Zaitoun. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class PannableScrollView: UIScrollView,UIScrollViewDelegate { 12 | 13 | open var view: UIView! { 14 | didSet { 15 | setupView() 16 | } 17 | } 18 | 19 | override public var frame: CGRect { 20 | didSet { 21 | if frame.size != oldValue.size { setZoomScale() } 22 | } 23 | } 24 | 25 | public override init(frame: CGRect) { 26 | super.init(frame: frame) 27 | 28 | if #available(iOS 11.0, *) { 29 | contentInsetAdjustmentBehavior = .never 30 | } 31 | showsVerticalScrollIndicator = false 32 | showsHorizontalScrollIndicator = false 33 | alwaysBounceHorizontal = true 34 | alwaysBounceVertical = true 35 | } 36 | 37 | func setupView() { 38 | for view in subviews { view.removeFromSuperview() } 39 | view.sizeToFit() 40 | addSubview(view) 41 | contentSize = view.bounds.size 42 | 43 | } 44 | 45 | public required init?(coder aDecoder: NSCoder) { 46 | fatalError("init(coder:) has not been implemented") 47 | } 48 | 49 | public func setZoomScale() { 50 | view.sizeToFit() 51 | view.frame.origin = .zero 52 | let widthScale = frame.size.width / view.bounds.width 53 | let heightScale = frame.size.height / view.bounds.height 54 | let minScale = min(widthScale, heightScale) 55 | minimumZoomScale = minScale 56 | zoomScale = minScale 57 | } 58 | 59 | public func viewForZooming(in scrollView: UIScrollView) -> UIView? { 60 | return view 61 | } 62 | 63 | public func scrollViewDidZoom(_ scrollView: UIScrollView) { 64 | if let view = view { 65 | let viewSize = view.frame.size 66 | let scrollViewSize = scrollView.bounds.size 67 | let verticalInset = viewSize.height < scrollViewSize.height ? (scrollViewSize.height - viewSize.height) / 2 : 0 68 | let horizontalInset = viewSize.width < scrollViewSize.width ? (scrollViewSize.width - viewSize.width) / 2 : 0 69 | scrollView.contentInset = UIEdgeInsets(top: verticalInset, left: horizontalInset, bottom: verticalInset, right: horizontalInset) 70 | } 71 | } 72 | } 73 | --------------------------------------------------------------------------------