├── .gitignore ├── .gitmodules ├── Carthage └── Build │ └── iOS │ └── Haneke.framework.dSYM │ └── Contents │ ├── Info.plist │ └── Resources │ └── DWARF │ └── Haneke ├── ImageViewer.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ └── ImageViewer.xccheckout │ └── xcuserdata │ │ ├── quentindequelen.xcuserdatad │ │ └── UserInterfaceState.xcuserstate │ │ └── tannghiala.xcuserdatad │ │ └── UserInterfaceState.xcuserstate ├── xcshareddata │ └── xcschemes │ │ └── ImageViewer.xcscheme └── xcuserdata │ ├── quentindequelen.xcuserdatad │ └── xcschemes │ │ └── xcschememanagement.plist │ └── tannghiala.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── ImageViewer ├── ImageViewer.swift ├── Images.xcassets │ └── Close.imageset │ │ ├── Close.png │ │ ├── Close@2x.png │ │ ├── Close@3x.png │ │ └── Contents.json ├── Info.plist └── UIImageView+ImageViewer.swift ├── ImageViewerTests ├── ImageViewerTests.swift └── Info.plist ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | Carthage/Build/iOS/ImageViewer.framework/Assets.car 2 | Carthage/Build/iOS/ImageViewer.framework/ImageViewer 3 | Carthage/Build/iOS/ImageViewer.framework/Info.plist 4 | Carthage/Build/iOS/ImageViewer.framework/_CodeSignature/CodeResources 5 | Carthage/Build/iOS/ImageViewer.framework/Frameworks/Haneke.framework/Haneke 6 | Carthage/Build/iOS/ImageViewer.framework/Frameworks/Haneke.framework/Info.plist 7 | Carthage/Build/iOS/ImageViewer.framework/Frameworks/Haneke.framework/_CodeSignature/CodeResources 8 | Carthage/Build/iOS/ImageViewer.framework/Frameworks/Haneke.framework/Headers/Haneke-Swift.h 9 | Carthage/Build/iOS/ImageViewer.framework/Frameworks/Haneke.framework/Headers/Haneke.h 10 | Carthage/Build/iOS/ImageViewer.framework/Frameworks/Haneke.framework/Modules/module.modulemap 11 | Carthage/Build/iOS/ImageViewer.framework/Frameworks/Haneke.framework/Modules/Haneke.swiftmodule/arm.swiftdoc 12 | Carthage/Build/iOS/ImageViewer.framework/Frameworks/Haneke.framework/Modules/Haneke.swiftmodule/arm.swiftmodule 13 | Carthage/Build/iOS/ImageViewer.framework/Frameworks/Haneke.framework/Modules/Haneke.swiftmodule/arm64.swiftdoc 14 | Carthage/Build/iOS/ImageViewer.framework/Frameworks/Haneke.framework/Modules/Haneke.swiftmodule/arm64.swiftmodule 15 | Carthage/Build/iOS/ImageViewer.framework/Frameworks/Haneke.framework/Modules/Haneke.swiftmodule/i386.swiftdoc 16 | Carthage/Build/iOS/ImageViewer.framework/Frameworks/Haneke.framework/Modules/Haneke.swiftmodule/i386.swiftmodule 17 | Carthage/Build/iOS/ImageViewer.framework/Frameworks/Haneke.framework/Modules/Haneke.swiftmodule/x86_64.swiftdoc 18 | Carthage/Build/iOS/ImageViewer.framework/Frameworks/Haneke.framework/Modules/Haneke.swiftmodule/x86_64.swiftmodule 19 | Carthage/Build/iOS/ImageViewer.framework/Headers/ImageViewer-Swift.h 20 | Carthage/Build/iOS/ImageViewer.framework/Headers/ImageViewer.h 21 | Carthage/Build/iOS/ImageViewer.framework/Modules/module.modulemap 22 | Carthage/Build/iOS/ImageViewer.framework/Modules/ImageViewer.swiftmodule/arm.swiftdoc 23 | Carthage/Build/iOS/ImageViewer.framework/Modules/ImageViewer.swiftmodule/arm.swiftmodule 24 | Carthage/Build/iOS/ImageViewer.framework/Modules/ImageViewer.swiftmodule/arm64.swiftdoc 25 | Carthage/Build/iOS/ImageViewer.framework/Modules/ImageViewer.swiftmodule/arm64.swiftmodule 26 | Carthage/Build/iOS/ImageViewer.framework/Modules/ImageViewer.swiftmodule/i386.swiftdoc 27 | Carthage/Build/iOS/ImageViewer.framework/Modules/ImageViewer.swiftmodule/i386.swiftmodule 28 | Carthage/Build/iOS/ImageViewer.framework/Modules/ImageViewer.swiftmodule/x86_64.swiftdoc 29 | Carthage/Build/iOS/ImageViewer.framework/Modules/ImageViewer.swiftmodule/x86_64.swiftmodule 30 | .DS_Store 31 | **/.DS_Store -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Carthage/Checkouts/HanekeSwift"] 2 | path = Carthage/Checkouts/HanekeSwift 3 | url = https://github.com/Haneke/HanekeSwift.git 4 | -------------------------------------------------------------------------------- /Carthage/Build/iOS/Haneke.framework.dSYM/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleIdentifier 8 | com.apple.xcode.dsym.io.haneke.Haneke 9 | CFBundleInfoDictionaryVersion 10 | 6.0 11 | CFBundlePackageType 12 | dSYM 13 | CFBundleSignature 14 | ???? 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleVersion 18 | 1 19 | 20 | 21 | -------------------------------------------------------------------------------- /Carthage/Build/iOS/Haneke.framework.dSYM/Contents/Resources/DWARF/Haneke: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mollywoodnini/ImageViewer/8fda7eb185561c1d3c46a8b8426f0610e5b59272/Carthage/Build/iOS/Haneke.framework.dSYM/Contents/Resources/DWARF/Haneke -------------------------------------------------------------------------------- /ImageViewer.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C68A2CCC1AF64F68000E8A94 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C68A2CCB1AF64F68000E8A94 /* Images.xcassets */; }; 11 | C6A0AC131AF641D800B79957 /* ImageViewer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C6A0AC071AF641D800B79957 /* ImageViewer.framework */; }; 12 | C6A0AC1A1AF641D800B79957 /* ImageViewerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A0AC191AF641D800B79957 /* ImageViewerTests.swift */; }; 13 | C6A0AC241AF6424400B79957 /* UIImageView+ImageViewer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A0AC231AF6424400B79957 /* UIImageView+ImageViewer.swift */; }; 14 | C6A0AC261AF6426600B79957 /* ImageViewer.swift in Sources */ = {isa = PBXBuildFile; fileRef = C6A0AC251AF6426600B79957 /* ImageViewer.swift */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXContainerItemProxy section */ 18 | C6A0AC141AF641D800B79957 /* PBXContainerItemProxy */ = { 19 | isa = PBXContainerItemProxy; 20 | containerPortal = C6A0ABFE1AF641D800B79957 /* Project object */; 21 | proxyType = 1; 22 | remoteGlobalIDString = C6A0AC061AF641D800B79957; 23 | remoteInfo = ImageViewer; 24 | }; 25 | /* End PBXContainerItemProxy section */ 26 | 27 | /* Begin PBXFileReference section */ 28 | C68A2CCB1AF64F68000E8A94 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 29 | C6A0AC071AF641D800B79957 /* ImageViewer.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ImageViewer.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 30 | C6A0AC0B1AF641D800B79957 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 31 | C6A0AC121AF641D800B79957 /* ImageViewerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ImageViewerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 32 | C6A0AC181AF641D800B79957 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 33 | C6A0AC191AF641D800B79957 /* ImageViewerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewerTests.swift; sourceTree = ""; }; 34 | C6A0AC231AF6424400B79957 /* UIImageView+ImageViewer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImageView+ImageViewer.swift"; sourceTree = ""; }; 35 | C6A0AC251AF6426600B79957 /* ImageViewer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageViewer.swift; sourceTree = ""; }; 36 | /* End PBXFileReference section */ 37 | 38 | /* Begin PBXFrameworksBuildPhase section */ 39 | C6A0AC031AF641D800B79957 /* Frameworks */ = { 40 | isa = PBXFrameworksBuildPhase; 41 | buildActionMask = 2147483647; 42 | files = ( 43 | ); 44 | runOnlyForDeploymentPostprocessing = 0; 45 | }; 46 | C6A0AC0F1AF641D800B79957 /* Frameworks */ = { 47 | isa = PBXFrameworksBuildPhase; 48 | buildActionMask = 2147483647; 49 | files = ( 50 | C6A0AC131AF641D800B79957 /* ImageViewer.framework in Frameworks */, 51 | ); 52 | runOnlyForDeploymentPostprocessing = 0; 53 | }; 54 | /* End PBXFrameworksBuildPhase section */ 55 | 56 | /* Begin PBXGroup section */ 57 | C6A0ABFD1AF641D800B79957 = { 58 | isa = PBXGroup; 59 | children = ( 60 | C6A0AC091AF641D800B79957 /* ImageViewer */, 61 | C6A0AC161AF641D800B79957 /* ImageViewerTests */, 62 | C6A0AC081AF641D800B79957 /* Products */, 63 | ); 64 | sourceTree = ""; 65 | }; 66 | C6A0AC081AF641D800B79957 /* Products */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | C6A0AC071AF641D800B79957 /* ImageViewer.framework */, 70 | C6A0AC121AF641D800B79957 /* ImageViewerTests.xctest */, 71 | ); 72 | name = Products; 73 | sourceTree = ""; 74 | }; 75 | C6A0AC091AF641D800B79957 /* ImageViewer */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | C6A0AC231AF6424400B79957 /* UIImageView+ImageViewer.swift */, 79 | C6A0AC251AF6426600B79957 /* ImageViewer.swift */, 80 | C6A0AC0A1AF641D800B79957 /* Supporting Files */, 81 | ); 82 | path = ImageViewer; 83 | sourceTree = ""; 84 | }; 85 | C6A0AC0A1AF641D800B79957 /* Supporting Files */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | C6A0AC0B1AF641D800B79957 /* Info.plist */, 89 | C68A2CCB1AF64F68000E8A94 /* Images.xcassets */, 90 | ); 91 | name = "Supporting Files"; 92 | sourceTree = ""; 93 | }; 94 | C6A0AC161AF641D800B79957 /* ImageViewerTests */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | C6A0AC191AF641D800B79957 /* ImageViewerTests.swift */, 98 | C6A0AC171AF641D800B79957 /* Supporting Files */, 99 | ); 100 | path = ImageViewerTests; 101 | sourceTree = ""; 102 | }; 103 | C6A0AC171AF641D800B79957 /* Supporting Files */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | C6A0AC181AF641D800B79957 /* Info.plist */, 107 | ); 108 | name = "Supporting Files"; 109 | sourceTree = ""; 110 | }; 111 | /* End PBXGroup section */ 112 | 113 | /* Begin PBXHeadersBuildPhase section */ 114 | C6A0AC041AF641D800B79957 /* Headers */ = { 115 | isa = PBXHeadersBuildPhase; 116 | buildActionMask = 2147483647; 117 | files = ( 118 | ); 119 | runOnlyForDeploymentPostprocessing = 0; 120 | }; 121 | /* End PBXHeadersBuildPhase section */ 122 | 123 | /* Begin PBXNativeTarget section */ 124 | C6A0AC061AF641D800B79957 /* ImageViewer */ = { 125 | isa = PBXNativeTarget; 126 | buildConfigurationList = C6A0AC1D1AF641D800B79957 /* Build configuration list for PBXNativeTarget "ImageViewer" */; 127 | buildPhases = ( 128 | C6A0AC021AF641D800B79957 /* Sources */, 129 | C6A0AC031AF641D800B79957 /* Frameworks */, 130 | C6A0AC041AF641D800B79957 /* Headers */, 131 | C6A0AC051AF641D800B79957 /* Resources */, 132 | C6805E111AF678BD005FB3EE /* ShellScript */, 133 | ); 134 | buildRules = ( 135 | ); 136 | dependencies = ( 137 | ); 138 | name = ImageViewer; 139 | productName = ImageViewer; 140 | productReference = C6A0AC071AF641D800B79957 /* ImageViewer.framework */; 141 | productType = "com.apple.product-type.framework"; 142 | }; 143 | C6A0AC111AF641D800B79957 /* ImageViewerTests */ = { 144 | isa = PBXNativeTarget; 145 | buildConfigurationList = C6A0AC201AF641D800B79957 /* Build configuration list for PBXNativeTarget "ImageViewerTests" */; 146 | buildPhases = ( 147 | C6A0AC0E1AF641D800B79957 /* Sources */, 148 | C6A0AC0F1AF641D800B79957 /* Frameworks */, 149 | C6A0AC101AF641D800B79957 /* Resources */, 150 | ); 151 | buildRules = ( 152 | ); 153 | dependencies = ( 154 | C6A0AC151AF641D800B79957 /* PBXTargetDependency */, 155 | ); 156 | name = ImageViewerTests; 157 | productName = ImageViewerTests; 158 | productReference = C6A0AC121AF641D800B79957 /* ImageViewerTests.xctest */; 159 | productType = "com.apple.product-type.bundle.unit-test"; 160 | }; 161 | /* End PBXNativeTarget section */ 162 | 163 | /* Begin PBXProject section */ 164 | C6A0ABFE1AF641D800B79957 /* Project object */ = { 165 | isa = PBXProject; 166 | attributes = { 167 | LastSwiftMigration = 0700; 168 | LastSwiftUpdateCheck = 0700; 169 | LastUpgradeCheck = 0700; 170 | ORGANIZATIONNAME = "Tan Nghia La"; 171 | TargetAttributes = { 172 | C6A0AC061AF641D800B79957 = { 173 | CreatedOnToolsVersion = 6.4; 174 | LastSwiftMigration = 0830; 175 | }; 176 | C6A0AC111AF641D800B79957 = { 177 | CreatedOnToolsVersion = 6.4; 178 | LastSwiftMigration = 0830; 179 | }; 180 | }; 181 | }; 182 | buildConfigurationList = C6A0AC011AF641D800B79957 /* Build configuration list for PBXProject "ImageViewer" */; 183 | compatibilityVersion = "Xcode 3.2"; 184 | developmentRegion = English; 185 | hasScannedForEncodings = 0; 186 | knownRegions = ( 187 | en, 188 | ); 189 | mainGroup = C6A0ABFD1AF641D800B79957; 190 | productRefGroup = C6A0AC081AF641D800B79957 /* Products */; 191 | projectDirPath = ""; 192 | projectRoot = ""; 193 | targets = ( 194 | C6A0AC061AF641D800B79957 /* ImageViewer */, 195 | C6A0AC111AF641D800B79957 /* ImageViewerTests */, 196 | ); 197 | }; 198 | /* End PBXProject section */ 199 | 200 | /* Begin PBXResourcesBuildPhase section */ 201 | C6A0AC051AF641D800B79957 /* Resources */ = { 202 | isa = PBXResourcesBuildPhase; 203 | buildActionMask = 2147483647; 204 | files = ( 205 | C68A2CCC1AF64F68000E8A94 /* Images.xcassets in Resources */, 206 | ); 207 | runOnlyForDeploymentPostprocessing = 0; 208 | }; 209 | C6A0AC101AF641D800B79957 /* Resources */ = { 210 | isa = PBXResourcesBuildPhase; 211 | buildActionMask = 2147483647; 212 | files = ( 213 | ); 214 | runOnlyForDeploymentPostprocessing = 0; 215 | }; 216 | /* End PBXResourcesBuildPhase section */ 217 | 218 | /* Begin PBXShellScriptBuildPhase section */ 219 | C6805E111AF678BD005FB3EE /* ShellScript */ = { 220 | isa = PBXShellScriptBuildPhase; 221 | buildActionMask = 2147483647; 222 | files = ( 223 | ); 224 | inputPaths = ( 225 | ); 226 | outputPaths = ( 227 | ); 228 | runOnlyForDeploymentPostprocessing = 0; 229 | shellPath = /bin/sh; 230 | shellScript = "/usr/local/bin/carthage copy-frameworks"; 231 | }; 232 | /* End PBXShellScriptBuildPhase section */ 233 | 234 | /* Begin PBXSourcesBuildPhase section */ 235 | C6A0AC021AF641D800B79957 /* Sources */ = { 236 | isa = PBXSourcesBuildPhase; 237 | buildActionMask = 2147483647; 238 | files = ( 239 | C6A0AC241AF6424400B79957 /* UIImageView+ImageViewer.swift in Sources */, 240 | C6A0AC261AF6426600B79957 /* ImageViewer.swift in Sources */, 241 | ); 242 | runOnlyForDeploymentPostprocessing = 0; 243 | }; 244 | C6A0AC0E1AF641D800B79957 /* Sources */ = { 245 | isa = PBXSourcesBuildPhase; 246 | buildActionMask = 2147483647; 247 | files = ( 248 | C6A0AC1A1AF641D800B79957 /* ImageViewerTests.swift in Sources */, 249 | ); 250 | runOnlyForDeploymentPostprocessing = 0; 251 | }; 252 | /* End PBXSourcesBuildPhase section */ 253 | 254 | /* Begin PBXTargetDependency section */ 255 | C6A0AC151AF641D800B79957 /* PBXTargetDependency */ = { 256 | isa = PBXTargetDependency; 257 | target = C6A0AC061AF641D800B79957 /* ImageViewer */; 258 | targetProxy = C6A0AC141AF641D800B79957 /* PBXContainerItemProxy */; 259 | }; 260 | /* End PBXTargetDependency section */ 261 | 262 | /* Begin XCBuildConfiguration section */ 263 | C6A0AC1B1AF641D800B79957 /* Debug */ = { 264 | isa = XCBuildConfiguration; 265 | buildSettings = { 266 | ALWAYS_SEARCH_USER_PATHS = NO; 267 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 268 | CLANG_CXX_LIBRARY = "libc++"; 269 | CLANG_ENABLE_MODULES = YES; 270 | CLANG_ENABLE_OBJC_ARC = YES; 271 | CLANG_WARN_BOOL_CONVERSION = YES; 272 | CLANG_WARN_CONSTANT_CONVERSION = YES; 273 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 274 | CLANG_WARN_EMPTY_BODY = YES; 275 | CLANG_WARN_ENUM_CONVERSION = YES; 276 | CLANG_WARN_INT_CONVERSION = YES; 277 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 278 | CLANG_WARN_UNREACHABLE_CODE = YES; 279 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 280 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 281 | COPY_PHASE_STRIP = NO; 282 | CURRENT_PROJECT_VERSION = 1; 283 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 284 | ENABLE_STRICT_OBJC_MSGSEND = YES; 285 | ENABLE_TESTABILITY = YES; 286 | GCC_C_LANGUAGE_STANDARD = gnu99; 287 | GCC_DYNAMIC_NO_PIC = NO; 288 | GCC_NO_COMMON_BLOCKS = YES; 289 | GCC_OPTIMIZATION_LEVEL = 0; 290 | GCC_PREPROCESSOR_DEFINITIONS = ( 291 | "DEBUG=1", 292 | "$(inherited)", 293 | ); 294 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 295 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 296 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 297 | GCC_WARN_UNDECLARED_SELECTOR = YES; 298 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 299 | GCC_WARN_UNUSED_FUNCTION = YES; 300 | GCC_WARN_UNUSED_VARIABLE = YES; 301 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 302 | MTL_ENABLE_DEBUG_INFO = YES; 303 | ONLY_ACTIVE_ARCH = YES; 304 | SDKROOT = iphoneos; 305 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 306 | TARGETED_DEVICE_FAMILY = "1,2"; 307 | VERSIONING_SYSTEM = "apple-generic"; 308 | VERSION_INFO_PREFIX = ""; 309 | }; 310 | name = Debug; 311 | }; 312 | C6A0AC1C1AF641D800B79957 /* Release */ = { 313 | isa = XCBuildConfiguration; 314 | buildSettings = { 315 | ALWAYS_SEARCH_USER_PATHS = NO; 316 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 317 | CLANG_CXX_LIBRARY = "libc++"; 318 | CLANG_ENABLE_MODULES = YES; 319 | CLANG_ENABLE_OBJC_ARC = YES; 320 | CLANG_WARN_BOOL_CONVERSION = YES; 321 | CLANG_WARN_CONSTANT_CONVERSION = YES; 322 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 323 | CLANG_WARN_EMPTY_BODY = YES; 324 | CLANG_WARN_ENUM_CONVERSION = YES; 325 | CLANG_WARN_INT_CONVERSION = YES; 326 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 327 | CLANG_WARN_UNREACHABLE_CODE = YES; 328 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 329 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 330 | COPY_PHASE_STRIP = NO; 331 | CURRENT_PROJECT_VERSION = 1; 332 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 333 | ENABLE_NS_ASSERTIONS = NO; 334 | ENABLE_STRICT_OBJC_MSGSEND = YES; 335 | GCC_C_LANGUAGE_STANDARD = gnu99; 336 | GCC_NO_COMMON_BLOCKS = YES; 337 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 338 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 339 | GCC_WARN_UNDECLARED_SELECTOR = YES; 340 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 341 | GCC_WARN_UNUSED_FUNCTION = YES; 342 | GCC_WARN_UNUSED_VARIABLE = YES; 343 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 344 | MTL_ENABLE_DEBUG_INFO = NO; 345 | SDKROOT = iphoneos; 346 | TARGETED_DEVICE_FAMILY = "1,2"; 347 | VALIDATE_PRODUCT = YES; 348 | VERSIONING_SYSTEM = "apple-generic"; 349 | VERSION_INFO_PREFIX = ""; 350 | }; 351 | name = Release; 352 | }; 353 | C6A0AC1E1AF641D800B79957 /* Debug */ = { 354 | isa = XCBuildConfiguration; 355 | buildSettings = { 356 | CLANG_ENABLE_MODULES = YES; 357 | DEFINES_MODULE = YES; 358 | DYLIB_COMPATIBILITY_VERSION = 1; 359 | DYLIB_CURRENT_VERSION = 1; 360 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 361 | FRAMEWORK_SEARCH_PATHS = ( 362 | "$(inherited)", 363 | "$(PROJECT_DIR)/Carthage/Build/iOS", 364 | ); 365 | INFOPLIST_FILE = ImageViewer/Info.plist; 366 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 367 | IPHONEOS_DEPLOYMENT_TARGET = 8.3; 368 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 369 | PRODUCT_BUNDLE_IDENTIFIER = "mollywoodnini.$(PRODUCT_NAME:rfc1034identifier)"; 370 | PRODUCT_NAME = "$(TARGET_NAME)"; 371 | SKIP_INSTALL = YES; 372 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 373 | SWIFT_VERSION = 3.0; 374 | }; 375 | name = Debug; 376 | }; 377 | C6A0AC1F1AF641D800B79957 /* Release */ = { 378 | isa = XCBuildConfiguration; 379 | buildSettings = { 380 | CLANG_ENABLE_MODULES = YES; 381 | DEFINES_MODULE = YES; 382 | DYLIB_COMPATIBILITY_VERSION = 1; 383 | DYLIB_CURRENT_VERSION = 1; 384 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 385 | FRAMEWORK_SEARCH_PATHS = ( 386 | "$(inherited)", 387 | "$(PROJECT_DIR)/Carthage/Build/iOS", 388 | ); 389 | INFOPLIST_FILE = ImageViewer/Info.plist; 390 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 391 | IPHONEOS_DEPLOYMENT_TARGET = 8.3; 392 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 393 | PRODUCT_BUNDLE_IDENTIFIER = "mollywoodnini.$(PRODUCT_NAME:rfc1034identifier)"; 394 | PRODUCT_NAME = "$(TARGET_NAME)"; 395 | SKIP_INSTALL = YES; 396 | SWIFT_VERSION = 3.0; 397 | }; 398 | name = Release; 399 | }; 400 | C6A0AC211AF641D800B79957 /* Debug */ = { 401 | isa = XCBuildConfiguration; 402 | buildSettings = { 403 | FRAMEWORK_SEARCH_PATHS = ( 404 | "$(SDKROOT)/Developer/Library/Frameworks", 405 | "$(inherited)", 406 | ); 407 | GCC_PREPROCESSOR_DEFINITIONS = ( 408 | "DEBUG=1", 409 | "$(inherited)", 410 | ); 411 | INFOPLIST_FILE = ImageViewerTests/Info.plist; 412 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 413 | PRODUCT_BUNDLE_IDENTIFIER = "mollywoodnini.$(PRODUCT_NAME:rfc1034identifier)"; 414 | PRODUCT_NAME = "$(TARGET_NAME)"; 415 | SWIFT_VERSION = 3.0; 416 | }; 417 | name = Debug; 418 | }; 419 | C6A0AC221AF641D800B79957 /* Release */ = { 420 | isa = XCBuildConfiguration; 421 | buildSettings = { 422 | FRAMEWORK_SEARCH_PATHS = ( 423 | "$(SDKROOT)/Developer/Library/Frameworks", 424 | "$(inherited)", 425 | ); 426 | INFOPLIST_FILE = ImageViewerTests/Info.plist; 427 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 428 | PRODUCT_BUNDLE_IDENTIFIER = "mollywoodnini.$(PRODUCT_NAME:rfc1034identifier)"; 429 | PRODUCT_NAME = "$(TARGET_NAME)"; 430 | SWIFT_VERSION = 3.0; 431 | }; 432 | name = Release; 433 | }; 434 | /* End XCBuildConfiguration section */ 435 | 436 | /* Begin XCConfigurationList section */ 437 | C6A0AC011AF641D800B79957 /* Build configuration list for PBXProject "ImageViewer" */ = { 438 | isa = XCConfigurationList; 439 | buildConfigurations = ( 440 | C6A0AC1B1AF641D800B79957 /* Debug */, 441 | C6A0AC1C1AF641D800B79957 /* Release */, 442 | ); 443 | defaultConfigurationIsVisible = 0; 444 | defaultConfigurationName = Release; 445 | }; 446 | C6A0AC1D1AF641D800B79957 /* Build configuration list for PBXNativeTarget "ImageViewer" */ = { 447 | isa = XCConfigurationList; 448 | buildConfigurations = ( 449 | C6A0AC1E1AF641D800B79957 /* Debug */, 450 | C6A0AC1F1AF641D800B79957 /* Release */, 451 | ); 452 | defaultConfigurationIsVisible = 0; 453 | defaultConfigurationName = Release; 454 | }; 455 | C6A0AC201AF641D800B79957 /* Build configuration list for PBXNativeTarget "ImageViewerTests" */ = { 456 | isa = XCConfigurationList; 457 | buildConfigurations = ( 458 | C6A0AC211AF641D800B79957 /* Debug */, 459 | C6A0AC221AF641D800B79957 /* Release */, 460 | ); 461 | defaultConfigurationIsVisible = 0; 462 | defaultConfigurationName = Release; 463 | }; 464 | /* End XCConfigurationList section */ 465 | }; 466 | rootObject = C6A0ABFE1AF641D800B79957 /* Project object */; 467 | } 468 | -------------------------------------------------------------------------------- /ImageViewer.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ImageViewer.xcodeproj/project.xcworkspace/xcshareddata/ImageViewer.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | B89EFB83-72CA-498B-BD2C-4B496920580C 9 | IDESourceControlProjectName 10 | ImageViewer 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | 37311F780D4092CEB0604EB681F1BC64903C6B2B 14 | https://github.com/mollywoodnini/ImageViewer.git 15 | 16 | IDESourceControlProjectPath 17 | ImageViewer.xcodeproj 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | 37311F780D4092CEB0604EB681F1BC64903C6B2B 21 | ../.. 22 | 23 | IDESourceControlProjectURL 24 | https://github.com/mollywoodnini/ImageViewer.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | 37311F780D4092CEB0604EB681F1BC64903C6B2B 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | 37311F780D4092CEB0604EB681F1BC64903C6B2B 36 | IDESourceControlWCCName 37 | ImageViewer 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /ImageViewer.xcodeproj/project.xcworkspace/xcuserdata/quentindequelen.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mollywoodnini/ImageViewer/8fda7eb185561c1d3c46a8b8426f0610e5b59272/ImageViewer.xcodeproj/project.xcworkspace/xcuserdata/quentindequelen.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /ImageViewer.xcodeproj/project.xcworkspace/xcuserdata/tannghiala.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mollywoodnini/ImageViewer/8fda7eb185561c1d3c46a8b8426f0610e5b59272/ImageViewer.xcodeproj/project.xcworkspace/xcuserdata/tannghiala.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /ImageViewer.xcodeproj/xcshareddata/xcschemes/ImageViewer.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 16 | 22 | 23 | 24 | 31 | 37 | 38 | 39 | 40 | 41 | 46 | 47 | 49 | 55 | 56 | 57 | 58 | 59 | 65 | 66 | 67 | 68 | 69 | 70 | 80 | 81 | 87 | 88 | 89 | 90 | 91 | 92 | 98 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /ImageViewer.xcodeproj/xcuserdata/quentindequelen.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SuppressBuildableAutocreation 6 | 7 | C6A0AC061AF641D800B79957 8 | 9 | primary 10 | 11 | 12 | C6A0AC111AF641D800B79957 13 | 14 | primary 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /ImageViewer.xcodeproj/xcuserdata/tannghiala.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /ImageViewer.xcodeproj/xcuserdata/tannghiala.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | ImageViewer.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | C6A0AC061AF641D800B79957 16 | 17 | primary 18 | 19 | 20 | C6A0AC111AF641D800B79957 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /ImageViewer/ImageViewer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageViewer.swift 3 | // ImageViewer 4 | // 5 | // Created by Tan Nghia La on 30.04.15. 6 | // Copyright (c) 2015 Tan Nghia La. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class ImageViewer: UIViewController { 12 | // MARK: - Properties 13 | fileprivate let kMinMaskViewAlpha: CGFloat = 0.3 14 | fileprivate let kMaxImageScale: CGFloat = 2.5 15 | fileprivate let kMinImageScale: CGFloat = 1.0 16 | 17 | fileprivate let senderView: UIImageView 18 | fileprivate var originalFrameRelativeToScreen: CGRect! 19 | fileprivate var rootViewController: UIViewController! 20 | fileprivate let imageView = UIImageView() 21 | fileprivate var panGesture: UIPanGestureRecognizer! 22 | fileprivate var panOrigin: CGPoint! 23 | 24 | fileprivate var isAnimating = false 25 | fileprivate var isLoaded = false 26 | 27 | fileprivate var closeButton = UIButton() 28 | fileprivate let windowBounds = UIScreen.main.bounds 29 | fileprivate let scrollView = UIScrollView() 30 | fileprivate let maskView = UIView() 31 | 32 | // MARK: - Lifecycle methods 33 | init(senderView: UIImageView, backgroundColor: UIColor) { 34 | self.senderView = senderView 35 | 36 | rootViewController = UIApplication.shared.keyWindow!.rootViewController! 37 | maskView.backgroundColor = backgroundColor 38 | 39 | super.init(nibName: nil, bundle: nil) 40 | } 41 | 42 | required init?(coder aDecoder: NSCoder) { 43 | fatalError("init(coder:) has not been implemented") 44 | } 45 | 46 | override func viewDidLoad() { 47 | super.viewDidLoad() 48 | 49 | configureView() 50 | configureMaskView() 51 | configureScrollView() 52 | configureCloseButton() 53 | configureImageView() 54 | configureConstraints() 55 | } 56 | 57 | // MARK: - View configuration 58 | fileprivate func configureScrollView() { 59 | scrollView.frame = windowBounds 60 | scrollView.delegate = self 61 | scrollView.minimumZoomScale = kMinImageScale 62 | scrollView.maximumZoomScale = kMaxImageScale 63 | scrollView.zoomScale = 1 64 | 65 | view.addSubview(scrollView) 66 | } 67 | 68 | fileprivate func configureMaskView() { 69 | maskView.frame = windowBounds 70 | maskView.alpha = 0.0 71 | 72 | view.insertSubview(maskView, at: 0) 73 | } 74 | 75 | fileprivate func configureCloseButton() { 76 | closeButton.alpha = 0.0 77 | 78 | let image = UIImage(named: "Close", in: Bundle(for: ImageViewer.self), compatibleWith: nil) 79 | 80 | closeButton.setImage(image, for: UIControlState()) 81 | closeButton.translatesAutoresizingMaskIntoConstraints = false 82 | closeButton.addTarget(self, action: #selector(ImageViewer.closeButtonTapped(_:)), for: UIControlEvents.touchUpInside) 83 | view.addSubview(closeButton) 84 | 85 | view.setNeedsUpdateConstraints() 86 | } 87 | 88 | fileprivate func configureView() { 89 | var originalFrame = senderView.convert(windowBounds, to: nil) 90 | originalFrame.origin = CGPoint(x: originalFrame.origin.x, y: originalFrame.origin.y) 91 | originalFrame.size = senderView.frame.size 92 | 93 | originalFrameRelativeToScreen = originalFrame 94 | 95 | UIApplication.shared.setStatusBarHidden(true, with: UIStatusBarAnimation.slide) 96 | } 97 | 98 | fileprivate func configureImageView() { 99 | senderView.alpha = 0.0 100 | 101 | imageView.frame = originalFrameRelativeToScreen 102 | imageView.isUserInteractionEnabled = true 103 | imageView.contentMode = UIViewContentMode.scaleAspectFit 104 | 105 | imageView.image = senderView.image 106 | 107 | scrollView.addSubview(imageView) 108 | 109 | animateEntry() 110 | addPanGestureToView() 111 | addGestures() 112 | 113 | centerScrollViewContents() 114 | } 115 | 116 | fileprivate func configureConstraints() { 117 | var constraints: [NSLayoutConstraint] = [] 118 | 119 | let views: [String: UIView] = [ 120 | "closeButton": closeButton 121 | ] 122 | 123 | constraints.append(NSLayoutConstraint(item: closeButton, attribute: .centerX, relatedBy: .equal, toItem: closeButton.superview, attribute: .centerX, multiplier: 1.0, constant: 0)) 124 | constraints.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat: "V:[closeButton(==64)]-40-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views)) 125 | constraints.append(contentsOf: NSLayoutConstraint.constraints(withVisualFormat: "H:[closeButton(==64)]", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views)) 126 | 127 | NSLayoutConstraint.activate(constraints) 128 | } 129 | 130 | // MARK: - Gestures 131 | fileprivate func addPanGestureToView() { 132 | panGesture = UIPanGestureRecognizer(target: self, action: #selector(ImageViewer.gestureRecognizerDidPan(_:))) 133 | panGesture.cancelsTouchesInView = false 134 | panGesture.delegate = self 135 | 136 | imageView.addGestureRecognizer(panGesture) 137 | } 138 | 139 | fileprivate func addGestures() { 140 | let singleTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(ImageViewer.didSingleTap(_:))) 141 | singleTapRecognizer.numberOfTapsRequired = 1 142 | singleTapRecognizer.numberOfTouchesRequired = 1 143 | scrollView.addGestureRecognizer(singleTapRecognizer) 144 | 145 | let doubleTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(ImageViewer.didDoubleTap(_:))) 146 | doubleTapRecognizer.numberOfTapsRequired = 2 147 | doubleTapRecognizer.numberOfTouchesRequired = 1 148 | scrollView.addGestureRecognizer(doubleTapRecognizer) 149 | 150 | singleTapRecognizer.require(toFail: doubleTapRecognizer) 151 | } 152 | 153 | fileprivate func zoomInZoomOut(_ point: CGPoint) { 154 | let newZoomScale = scrollView.zoomScale > (scrollView.maximumZoomScale / 2) ? scrollView.minimumZoomScale : scrollView.maximumZoomScale 155 | 156 | let scrollViewSize = scrollView.bounds.size 157 | let w = scrollViewSize.width / newZoomScale 158 | let h = scrollViewSize.height / newZoomScale 159 | let x = point.x - (w / 2.0) 160 | let y = point.y - (h / 2.0) 161 | 162 | let rectToZoomTo = CGRect(x: x, y: y, width: w, height: h) 163 | 164 | scrollView.zoom(to: rectToZoomTo, animated: true) 165 | } 166 | 167 | // MARK: - Animation 168 | fileprivate func animateEntry() { 169 | guard let image = imageView.image else { 170 | fatalError("no image provided") 171 | } 172 | 173 | UIView.animate(withDuration: 0.8, delay: 0.0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.6, options: UIViewAnimationOptions.beginFromCurrentState, animations: {() -> Void in 174 | self.imageView.frame = self.centerFrameFromImage(image) 175 | }, completion: nil) 176 | 177 | UIView.animate(withDuration: 0.4, delay: 0.03, options: UIViewAnimationOptions.beginFromCurrentState, animations: {() -> Void in 178 | self.closeButton.alpha = 1.0 179 | self.maskView.alpha = 1.0 180 | }, completion: nil) 181 | 182 | UIView.animate(withDuration: 0.4, delay: 0.1, options: UIViewAnimationOptions.beginFromCurrentState, animations: {() -> Void in 183 | self.view.transform = CGAffineTransform.identity.scaledBy(x: 1.1, y: 1.1) 184 | self.rootViewController.view.transform = CGAffineTransform.identity.scaledBy(x: 0.95, y: 0.95) 185 | }, completion: nil) 186 | } 187 | 188 | fileprivate func centerFrameFromImage(_ image: UIImage) -> CGRect { 189 | var newImageSize = imageResizeBaseOnWidth(windowBounds.size.width, oldWidth: image.size.width, oldHeight: image.size.height) 190 | newImageSize.height = min(windowBounds.size.height, newImageSize.height) 191 | 192 | return CGRect(x: 0, y: windowBounds.size.height / 2 - newImageSize.height / 2, width: newImageSize.width, height: newImageSize.height) 193 | } 194 | 195 | fileprivate func imageResizeBaseOnWidth(_ newWidth: CGFloat, oldWidth: CGFloat, oldHeight: CGFloat) -> CGSize { 196 | let scaleFactor = newWidth / oldWidth 197 | let newHeight = oldHeight * scaleFactor 198 | 199 | return CGSize(width: newWidth, height: newHeight) 200 | } 201 | 202 | // MARK: - Actions 203 | func gestureRecognizerDidPan(_ recognizer: UIPanGestureRecognizer) { 204 | if scrollView.zoomScale != 1.0 || isAnimating { 205 | return 206 | } 207 | 208 | senderView.alpha = 0.0 209 | 210 | scrollView.bounces = false 211 | let windowSize = maskView.bounds.size 212 | let currentPoint = panGesture.translation(in: scrollView) 213 | let y = currentPoint.y + panOrigin.y 214 | 215 | imageView.frame.origin = CGPoint(x: currentPoint.x + panOrigin.x, y: y) 216 | 217 | let yDiff = abs((y + imageView.frame.size.height / 2) - windowSize.height / 2) 218 | maskView.alpha = max(1 - yDiff / (windowSize.height / 0.95), kMinMaskViewAlpha) 219 | closeButton.alpha = max(1 - yDiff / (windowSize.height / 0.95), kMinMaskViewAlpha) / 2 220 | 221 | if (panGesture.state == UIGestureRecognizerState.ended || panGesture.state == UIGestureRecognizerState.cancelled) 222 | && scrollView.zoomScale == 1.0 { 223 | maskView.alpha < 0.85 ? dismissViewController() : rollbackViewController() 224 | } 225 | } 226 | 227 | func didSingleTap(_ recognizer: UITapGestureRecognizer) { 228 | scrollView.zoomScale == 1.0 ? dismissViewController() : scrollView.setZoomScale(1.0, animated: true) 229 | } 230 | 231 | func didDoubleTap(_ recognizer: UITapGestureRecognizer) { 232 | let pointInView = recognizer.location(in: imageView) 233 | zoomInZoomOut(pointInView) 234 | } 235 | 236 | func closeButtonTapped(_ sender: UIButton) { 237 | if scrollView.zoomScale != 1.0 { 238 | scrollView.setZoomScale(1.0, animated: true) 239 | } 240 | dismissViewController() 241 | } 242 | 243 | // MARK: - Misc. 244 | fileprivate func centerScrollViewContents() { 245 | let boundsSize = rootViewController.view.bounds.size 246 | var contentsFrame = imageView.frame 247 | 248 | if contentsFrame.size.width < boundsSize.width { 249 | contentsFrame.origin.x = (boundsSize.width - contentsFrame.size.width) / 2.0 250 | } else { 251 | contentsFrame.origin.x = 0.0 252 | } 253 | 254 | if contentsFrame.size.height < boundsSize.height { 255 | contentsFrame.origin.y = (boundsSize.height - contentsFrame.size.height) / 2.0 256 | } else { 257 | contentsFrame.origin.y = 0.0 258 | } 259 | 260 | imageView.frame = contentsFrame 261 | } 262 | 263 | fileprivate func rollbackViewController() { 264 | guard let image = imageView.image else { 265 | fatalError("no image provided") 266 | } 267 | 268 | isAnimating = true 269 | UIView.animate(withDuration: 0.8, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.6, options: UIViewAnimationOptions.beginFromCurrentState, animations: {() in 270 | self.imageView.frame = self.centerFrameFromImage(image) 271 | self.maskView.alpha = 1.0 272 | self.closeButton.alpha = 1.0 273 | }, completion: {(finished) in 274 | self.isAnimating = false 275 | }) 276 | } 277 | 278 | fileprivate func dismissViewController() { 279 | isAnimating = true 280 | DispatchQueue.main.async(execute: { 281 | self.imageView.clipsToBounds = true 282 | 283 | UIView.animate(withDuration: 0.2, animations: {() in 284 | self.closeButton.alpha = 0.0 285 | }) 286 | 287 | UIView.animate(withDuration: 0.8, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.6, options: UIViewAnimationOptions.beginFromCurrentState, animations: {() in 288 | self.imageView.frame = self.originalFrameRelativeToScreen 289 | self.rootViewController.view.transform = CGAffineTransform.identity.scaledBy(x: 1.0, y: 1.0) 290 | self.view.transform = CGAffineTransform.identity.scaledBy(x: 1.0, y: 1.0) 291 | self.maskView.alpha = 0.0 292 | UIApplication.shared.setStatusBarHidden(false, with: UIStatusBarAnimation.none) 293 | }, completion: {(finished) in 294 | self.willMove(toParentViewController: nil) 295 | self.view.removeFromSuperview() 296 | self.removeFromParentViewController() 297 | self.senderView.alpha = 1.0 298 | self.isAnimating = false 299 | }) 300 | }) 301 | } 302 | 303 | func presentFromRootViewController() { 304 | willMove(toParentViewController: rootViewController) 305 | rootViewController.view.addSubview(view) 306 | rootViewController.addChildViewController(self) 307 | didMove(toParentViewController: rootViewController) 308 | } 309 | } 310 | 311 | // MARK: - GestureRecognizer delegate 312 | extension ImageViewer: UIGestureRecognizerDelegate { 313 | func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool { 314 | panOrigin = imageView.frame.origin 315 | gestureRecognizer.isEnabled = true 316 | return !isAnimating 317 | } 318 | } 319 | 320 | // MARK: - ScrollView delegate 321 | extension ImageViewer: UIScrollViewDelegate { 322 | func viewForZooming(in scrollView: UIScrollView) -> UIView? { 323 | return imageView 324 | } 325 | 326 | func scrollViewDidZoom(_ scrollView: UIScrollView) { 327 | isAnimating = true 328 | centerScrollViewContents() 329 | } 330 | 331 | func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) { 332 | isAnimating = false 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /ImageViewer/Images.xcassets/Close.imageset/Close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mollywoodnini/ImageViewer/8fda7eb185561c1d3c46a8b8426f0610e5b59272/ImageViewer/Images.xcassets/Close.imageset/Close.png -------------------------------------------------------------------------------- /ImageViewer/Images.xcassets/Close.imageset/Close@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mollywoodnini/ImageViewer/8fda7eb185561c1d3c46a8b8426f0610e5b59272/ImageViewer/Images.xcassets/Close.imageset/Close@2x.png -------------------------------------------------------------------------------- /ImageViewer/Images.xcassets/Close.imageset/Close@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mollywoodnini/ImageViewer/8fda7eb185561c1d3c46a8b8426f0610e5b59272/ImageViewer/Images.xcassets/Close.imageset/Close@3x.png -------------------------------------------------------------------------------- /ImageViewer/Images.xcassets/Close.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x", 6 | "filename" : "Close.png" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x", 11 | "filename" : "Close@2x.png" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x", 16 | "filename" : "Close@3x.png" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /ImageViewer/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ImageViewer/UIImageView+ImageViewer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImageView+ImageViewer.swift 3 | // ImageViewer 4 | // 5 | // Created by Tan Nghia La on 03.05.15. 6 | // Copyright (c) 2015 Tan Nghia La. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | public extension UIImageView { 13 | 14 | public func setupForImageViewer(_ backgroundColor: UIColor = UIColor.white) { 15 | isUserInteractionEnabled = true 16 | let gestureRecognizer = ImageViewerTapGestureRecognizer(target: self, action: #selector(UIImageView.didTap(_:)), backgroundColor: backgroundColor) 17 | addGestureRecognizer(gestureRecognizer) 18 | } 19 | 20 | internal func didTap(_ recognizer: ImageViewerTapGestureRecognizer) { 21 | let imageViewer = ImageViewer(senderView: self, backgroundColor: recognizer.backgroundColor) 22 | imageViewer.presentFromRootViewController() 23 | } 24 | } 25 | 26 | class ImageViewerTapGestureRecognizer: UITapGestureRecognizer { 27 | let backgroundColor: UIColor 28 | 29 | init(target: AnyObject, action: Selector, backgroundColor: UIColor) { 30 | self.backgroundColor = backgroundColor 31 | 32 | super.init(target: target, action: action) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /ImageViewerTests/ImageViewerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageViewerTests.swift 3 | // ImageViewerTests 4 | // 5 | // Created by Tan Nghia La on 03.05.15. 6 | // Copyright (c) 2015 Tan Nghia La. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import XCTest 11 | 12 | class ImageViewerTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | XCTAssert(true, "Pass") 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure() { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /ImageViewerTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Tan Nghia La 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ImageViewer 2 | 3 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 4 | 5 | The ImageViewer is a very simple image viewer for iOS, which behaviour is inspired by the facebook app and completely written in Swift 2.0. 6 | 7 | ## Screenshots 8 | ![Preview](https://cloud.githubusercontent.com/assets/5786065/7445805/72750532-f1c2-11e4-8944-b213bccbce73.gif 9 | ) ![Preview](https://cloud.githubusercontent.com/assets/5786065/7445806/7277981a-f1c2-11e4-9e08-bdc488f8b6a3.gif 10 | ) 11 | 12 | 13 | 14 | ## Installation 15 | Using [Carthage](https://github.com/Carthage/Carthage): 16 | 17 | 1. Create a cartfile with following content within your project directory: `github "mollywoodnini/ImageViewer"` 18 | 2. run `carthage update`. Carthage builds the ImageViewer framework, which you'll have to import to your project. 19 | 3. On your application targets’ “General” settings tab, in the “Linked Frameworks and Libraries” section, drag and drop both frameworks from the Carthage/Build folder on disk. 20 | 4. On your application targets’ “Build Phases” settings tab, click the “+” icon and choose “New Run Script Phase”. Create a Run Script with the following contents: 21 | ``` 22 | /usr/local/bin/carthage copy-frameworks 23 | ``` 24 | and add the paths to the frameworks under “Input Files”: 25 | ``` 26 | $(SRCROOT)/Carthage/Build/iOS/ImageViewer.framework 27 | ``` 28 | 29 | ## Usage 30 | Just create an ImageView and prepare it for the ImageViewer via: 31 | ```swift 32 | imageView.setupForImageViewer() 33 | ``` 34 | 35 | You can specify the background color of the ImageViewer too: 36 | ```swift 37 | imageView.setupForImageViewer(UIColor.redColor()) 38 | ``` 39 | 40 | To hide the status bar add the "View controller-based status bar appearance" key to the Info.plist and set its value to NO. 41 | 42 | ## Requirements 43 | - iOS 8.0+ 44 | - Xcode 7.0+ 45 | 46 | ## License 47 | 48 | Released under the MIT license. See the LICENSE file for more info. 49 | 50 | ## Credits 51 | - Thanks to icons8.com for the close image! 52 | - Thanks to Michael Henry, whose ImageViewer was a huge help for me to get this here done. 53 | --------------------------------------------------------------------------------