├── .gitignore ├── CHANGELOG.md ├── Example ├── Example.xcodeproj │ └── project.pbxproj └── Example │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── demo_1.imageset │ │ ├── Contents.json │ │ └── demo_1@2x.png │ └── demo_2.imageset │ │ ├── Contents.json │ │ └── demo_2@2x.png │ ├── Base.lproj │ └── LaunchScreen.storyboard │ ├── ImageViewController.swift │ ├── Info.plist │ ├── STCollectionViewDemoController.swift │ ├── STDemoAppDelegate.swift │ ├── STMenuDemoViewController.swift │ ├── STScrollViewDemoController.swift │ ├── STTableViewDemoController.swift │ ├── STUIWebViewDemoController.swift │ ├── STViewDemoController.swift │ └── STWKWebViewDemoController.swift ├── LICENSE ├── README.md ├── SwViewCapture.podspec ├── SwViewCapture.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcshareddata │ └── xcschemes │ └── SwViewCapture.xcscheme ├── SwViewCapture.xcworkspace └── contents.xcworkspacedata ├── SwViewCapture ├── Info.plist ├── SwViewCapture.h ├── UIScrollView+SwCapture.swift ├── UIView+SwCapture.swift └── WKWebView+SwCapture.swift └── capture_demo.gif /.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 | *.xccheckout 22 | *.moved-aside 23 | *.xcuserstate 24 | *.xcscmblueprint 25 | 26 | ## Obj-C/Swift specific 27 | *.hmap 28 | *.ipa 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | # Packages/ 38 | .build/ 39 | 40 | # CocoaPods 41 | # 42 | # We recommend against adding the Pods directory to your .gitignore. However 43 | # you should judge for yourself, the pros and cons are mentioned at: 44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 45 | # 46 | # Pods/ 47 | 48 | # Carthage 49 | # 50 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 51 | # Carthage/Checkouts 52 | 53 | Carthage/Build 54 | 55 | # fastlane 56 | # 57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 58 | # screenshots whenever they are needed. 59 | # For more information about the recommended setup visit: 60 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 61 | 62 | fastlane/report.xml 63 | fastlane/screenshots 64 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 1.0.0 2 | 3 | * init project 4 | * support capture UIScrollView & UIWebView & WKWebView content 5 | 6 | 1.0.1 7 | 8 | * fixed device scale problem 9 | 10 | 1.0.2 11 | 12 | * WKWebView Screenshots perfect support 13 | * WKWebView support two difference method to capture (scroll & not scroll) 14 | 15 | 1.0.3 16 | 17 | * optimize WKWebView Screenshots, fixed NavigationBar offset problem 18 | 19 | 1.0.4 20 | 21 | * Fix Crash Bug with nil return 22 | 23 | 1.0.5 24 | 25 | * Compatible to Swift 3.0 which contributed by [Lafree317](https://github.com/Lafree317). 26 | 27 | 1.0.6 28 | 29 | * fixed the problem that isCapurting always return false -------------------------------------------------------------------------------- /Example/Example.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 3916547E1EE7C94200A2E90B /* STCollectionViewDemoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3916547D1EE7C94200A2E90B /* STCollectionViewDemoController.swift */; }; 11 | 394BA34D1C71B6BF0012FCB5 /* SwViewCapture.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 39D667C51BE2053600BF0BD7 /* SwViewCapture.framework */; }; 12 | 394BA34F1C71B7300012FCB5 /* SwViewCapture.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 39D667C51BE2053600BF0BD7 /* SwViewCapture.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 13 | 394BA3541C71D1120012FCB5 /* STUIWebViewDemoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 394BA3531C71D1120012FCB5 /* STUIWebViewDemoController.swift */; }; 14 | 394BA3611C7461880012FCB5 /* ImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 394BA3601C7461880012FCB5 /* ImageViewController.swift */; }; 15 | 39D667A11BE202AC00BF0BD7 /* STDemoAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39D667A01BE202AC00BF0BD7 /* STDemoAppDelegate.swift */; }; 16 | 39D667A81BE202AC00BF0BD7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 39D667A71BE202AC00BF0BD7 /* Assets.xcassets */; }; 17 | 39D667AB1BE202AC00BF0BD7 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 39D667A91BE202AC00BF0BD7 /* LaunchScreen.storyboard */; }; 18 | 39D667B91BE202FE00BF0BD7 /* STMenuDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39D667B41BE202FE00BF0BD7 /* STMenuDemoViewController.swift */; }; 19 | 39D667BA1BE202FE00BF0BD7 /* STScrollViewDemoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39D667B51BE202FE00BF0BD7 /* STScrollViewDemoController.swift */; }; 20 | 39D667BB1BE202FE00BF0BD7 /* STTableViewDemoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39D667B61BE202FE00BF0BD7 /* STTableViewDemoController.swift */; }; 21 | 39D667BC1BE202FE00BF0BD7 /* STViewDemoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39D667B71BE202FE00BF0BD7 /* STViewDemoController.swift */; }; 22 | 39D667BD1BE202FE00BF0BD7 /* STWKWebViewDemoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39D667B81BE202FE00BF0BD7 /* STWKWebViewDemoController.swift */; }; 23 | /* End PBXBuildFile section */ 24 | 25 | /* Begin PBXContainerItemProxy section */ 26 | 394BA34A1C71B6A30012FCB5 /* PBXContainerItemProxy */ = { 27 | isa = PBXContainerItemProxy; 28 | containerPortal = 39D667C01BE2053600BF0BD7 /* SwViewCapture.xcodeproj */; 29 | proxyType = 1; 30 | remoteGlobalIDString = 39D667881BE2024900BF0BD7; 31 | remoteInfo = SwViewCapture; 32 | }; 33 | 394BA3501C71B7300012FCB5 /* PBXContainerItemProxy */ = { 34 | isa = PBXContainerItemProxy; 35 | containerPortal = 39D667C01BE2053600BF0BD7 /* SwViewCapture.xcodeproj */; 36 | proxyType = 1; 37 | remoteGlobalIDString = 39D667881BE2024900BF0BD7; 38 | remoteInfo = SwViewCapture; 39 | }; 40 | 39D667C41BE2053600BF0BD7 /* PBXContainerItemProxy */ = { 41 | isa = PBXContainerItemProxy; 42 | containerPortal = 39D667C01BE2053600BF0BD7 /* SwViewCapture.xcodeproj */; 43 | proxyType = 2; 44 | remoteGlobalIDString = 39D667891BE2024900BF0BD7; 45 | remoteInfo = SwViewCapture; 46 | }; 47 | /* End PBXContainerItemProxy section */ 48 | 49 | /* Begin PBXCopyFilesBuildPhase section */ 50 | 394BA3521C71B7300012FCB5 /* Embed Frameworks */ = { 51 | isa = PBXCopyFilesBuildPhase; 52 | buildActionMask = 2147483647; 53 | dstPath = ""; 54 | dstSubfolderSpec = 10; 55 | files = ( 56 | 394BA34F1C71B7300012FCB5 /* SwViewCapture.framework in Embed Frameworks */, 57 | ); 58 | name = "Embed Frameworks"; 59 | runOnlyForDeploymentPostprocessing = 0; 60 | }; 61 | /* End PBXCopyFilesBuildPhase section */ 62 | 63 | /* Begin PBXFileReference section */ 64 | 3916547D1EE7C94200A2E90B /* STCollectionViewDemoController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STCollectionViewDemoController.swift; sourceTree = ""; }; 65 | 394BA3531C71D1120012FCB5 /* STUIWebViewDemoController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STUIWebViewDemoController.swift; sourceTree = ""; }; 66 | 394BA3601C7461880012FCB5 /* ImageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageViewController.swift; sourceTree = ""; }; 67 | 39D6679D1BE202AC00BF0BD7 /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 68 | 39D667A01BE202AC00BF0BD7 /* STDemoAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = STDemoAppDelegate.swift; sourceTree = ""; }; 69 | 39D667A71BE202AC00BF0BD7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 70 | 39D667AA1BE202AC00BF0BD7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 71 | 39D667AC1BE202AC00BF0BD7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 72 | 39D667B41BE202FE00BF0BD7 /* STMenuDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STMenuDemoViewController.swift; sourceTree = ""; }; 73 | 39D667B51BE202FE00BF0BD7 /* STScrollViewDemoController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STScrollViewDemoController.swift; sourceTree = ""; }; 74 | 39D667B61BE202FE00BF0BD7 /* STTableViewDemoController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STTableViewDemoController.swift; sourceTree = ""; }; 75 | 39D667B71BE202FE00BF0BD7 /* STViewDemoController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STViewDemoController.swift; sourceTree = ""; }; 76 | 39D667B81BE202FE00BF0BD7 /* STWKWebViewDemoController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = STWKWebViewDemoController.swift; sourceTree = ""; }; 77 | 39D667C01BE2053600BF0BD7 /* SwViewCapture.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = SwViewCapture.xcodeproj; path = ../SwViewCapture.xcodeproj; sourceTree = ""; }; 78 | /* End PBXFileReference section */ 79 | 80 | /* Begin PBXFrameworksBuildPhase section */ 81 | 39D6679A1BE202AC00BF0BD7 /* Frameworks */ = { 82 | isa = PBXFrameworksBuildPhase; 83 | buildActionMask = 2147483647; 84 | files = ( 85 | 394BA34D1C71B6BF0012FCB5 /* SwViewCapture.framework in Frameworks */, 86 | ); 87 | runOnlyForDeploymentPostprocessing = 0; 88 | }; 89 | /* End PBXFrameworksBuildPhase section */ 90 | 91 | /* Begin PBXGroup section */ 92 | 39D667941BE202AC00BF0BD7 = { 93 | isa = PBXGroup; 94 | children = ( 95 | 39D667C01BE2053600BF0BD7 /* SwViewCapture.xcodeproj */, 96 | 39D6679F1BE202AC00BF0BD7 /* Example */, 97 | 39D6679E1BE202AC00BF0BD7 /* Products */, 98 | ); 99 | sourceTree = ""; 100 | }; 101 | 39D6679E1BE202AC00BF0BD7 /* Products */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | 39D6679D1BE202AC00BF0BD7 /* Example.app */, 105 | ); 106 | name = Products; 107 | sourceTree = ""; 108 | }; 109 | 39D6679F1BE202AC00BF0BD7 /* Example */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | 39D667A01BE202AC00BF0BD7 /* STDemoAppDelegate.swift */, 113 | 39D667B41BE202FE00BF0BD7 /* STMenuDemoViewController.swift */, 114 | 39D667B51BE202FE00BF0BD7 /* STScrollViewDemoController.swift */, 115 | 39D667B61BE202FE00BF0BD7 /* STTableViewDemoController.swift */, 116 | 39D667B71BE202FE00BF0BD7 /* STViewDemoController.swift */, 117 | 39D667B81BE202FE00BF0BD7 /* STWKWebViewDemoController.swift */, 118 | 394BA3531C71D1120012FCB5 /* STUIWebViewDemoController.swift */, 119 | 394BA3601C7461880012FCB5 /* ImageViewController.swift */, 120 | 39D667A71BE202AC00BF0BD7 /* Assets.xcassets */, 121 | 39D667A91BE202AC00BF0BD7 /* LaunchScreen.storyboard */, 122 | 39D667AC1BE202AC00BF0BD7 /* Info.plist */, 123 | 3916547D1EE7C94200A2E90B /* STCollectionViewDemoController.swift */, 124 | ); 125 | path = Example; 126 | sourceTree = ""; 127 | }; 128 | 39D667C11BE2053600BF0BD7 /* Products */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | 39D667C51BE2053600BF0BD7 /* SwViewCapture.framework */, 132 | ); 133 | name = Products; 134 | sourceTree = ""; 135 | }; 136 | /* End PBXGroup section */ 137 | 138 | /* Begin PBXNativeTarget section */ 139 | 39D6679C1BE202AC00BF0BD7 /* Example */ = { 140 | isa = PBXNativeTarget; 141 | buildConfigurationList = 39D667AF1BE202AC00BF0BD7 /* Build configuration list for PBXNativeTarget "Example" */; 142 | buildPhases = ( 143 | 39D667991BE202AC00BF0BD7 /* Sources */, 144 | 39D6679A1BE202AC00BF0BD7 /* Frameworks */, 145 | 39D6679B1BE202AC00BF0BD7 /* Resources */, 146 | 394BA3521C71B7300012FCB5 /* Embed Frameworks */, 147 | ); 148 | buildRules = ( 149 | ); 150 | dependencies = ( 151 | 394BA34B1C71B6A30012FCB5 /* PBXTargetDependency */, 152 | 394BA3511C71B7300012FCB5 /* PBXTargetDependency */, 153 | ); 154 | name = Example; 155 | productName = Example; 156 | productReference = 39D6679D1BE202AC00BF0BD7 /* Example.app */; 157 | productType = "com.apple.product-type.application"; 158 | }; 159 | /* End PBXNativeTarget section */ 160 | 161 | /* Begin PBXProject section */ 162 | 39D667951BE202AC00BF0BD7 /* Project object */ = { 163 | isa = PBXProject; 164 | attributes = { 165 | LastSwiftUpdateCheck = 0710; 166 | LastUpgradeCheck = 0820; 167 | ORGANIZATIONNAME = Startry; 168 | TargetAttributes = { 169 | 39D6679C1BE202AC00BF0BD7 = { 170 | CreatedOnToolsVersion = 7.1; 171 | LastSwiftMigration = 0800; 172 | }; 173 | }; 174 | }; 175 | buildConfigurationList = 39D667981BE202AC00BF0BD7 /* Build configuration list for PBXProject "Example" */; 176 | compatibilityVersion = "Xcode 3.2"; 177 | developmentRegion = English; 178 | hasScannedForEncodings = 0; 179 | knownRegions = ( 180 | en, 181 | Base, 182 | ); 183 | mainGroup = 39D667941BE202AC00BF0BD7; 184 | productRefGroup = 39D6679E1BE202AC00BF0BD7 /* Products */; 185 | projectDirPath = ""; 186 | projectReferences = ( 187 | { 188 | ProductGroup = 39D667C11BE2053600BF0BD7 /* Products */; 189 | ProjectRef = 39D667C01BE2053600BF0BD7 /* SwViewCapture.xcodeproj */; 190 | }, 191 | ); 192 | projectRoot = ""; 193 | targets = ( 194 | 39D6679C1BE202AC00BF0BD7 /* Example */, 195 | ); 196 | }; 197 | /* End PBXProject section */ 198 | 199 | /* Begin PBXReferenceProxy section */ 200 | 39D667C51BE2053600BF0BD7 /* SwViewCapture.framework */ = { 201 | isa = PBXReferenceProxy; 202 | fileType = wrapper.framework; 203 | path = SwViewCapture.framework; 204 | remoteRef = 39D667C41BE2053600BF0BD7 /* PBXContainerItemProxy */; 205 | sourceTree = BUILT_PRODUCTS_DIR; 206 | }; 207 | /* End PBXReferenceProxy section */ 208 | 209 | /* Begin PBXResourcesBuildPhase section */ 210 | 39D6679B1BE202AC00BF0BD7 /* Resources */ = { 211 | isa = PBXResourcesBuildPhase; 212 | buildActionMask = 2147483647; 213 | files = ( 214 | 39D667AB1BE202AC00BF0BD7 /* LaunchScreen.storyboard in Resources */, 215 | 39D667A81BE202AC00BF0BD7 /* Assets.xcassets in Resources */, 216 | ); 217 | runOnlyForDeploymentPostprocessing = 0; 218 | }; 219 | /* End PBXResourcesBuildPhase section */ 220 | 221 | /* Begin PBXSourcesBuildPhase section */ 222 | 39D667991BE202AC00BF0BD7 /* Sources */ = { 223 | isa = PBXSourcesBuildPhase; 224 | buildActionMask = 2147483647; 225 | files = ( 226 | 39D667BC1BE202FE00BF0BD7 /* STViewDemoController.swift in Sources */, 227 | 39D667BD1BE202FE00BF0BD7 /* STWKWebViewDemoController.swift in Sources */, 228 | 3916547E1EE7C94200A2E90B /* STCollectionViewDemoController.swift in Sources */, 229 | 394BA3541C71D1120012FCB5 /* STUIWebViewDemoController.swift in Sources */, 230 | 39D667A11BE202AC00BF0BD7 /* STDemoAppDelegate.swift in Sources */, 231 | 39D667BA1BE202FE00BF0BD7 /* STScrollViewDemoController.swift in Sources */, 232 | 39D667B91BE202FE00BF0BD7 /* STMenuDemoViewController.swift in Sources */, 233 | 394BA3611C7461880012FCB5 /* ImageViewController.swift in Sources */, 234 | 39D667BB1BE202FE00BF0BD7 /* STTableViewDemoController.swift in Sources */, 235 | ); 236 | runOnlyForDeploymentPostprocessing = 0; 237 | }; 238 | /* End PBXSourcesBuildPhase section */ 239 | 240 | /* Begin PBXTargetDependency section */ 241 | 394BA34B1C71B6A30012FCB5 /* PBXTargetDependency */ = { 242 | isa = PBXTargetDependency; 243 | name = SwViewCapture; 244 | targetProxy = 394BA34A1C71B6A30012FCB5 /* PBXContainerItemProxy */; 245 | }; 246 | 394BA3511C71B7300012FCB5 /* PBXTargetDependency */ = { 247 | isa = PBXTargetDependency; 248 | name = SwViewCapture; 249 | targetProxy = 394BA3501C71B7300012FCB5 /* PBXContainerItemProxy */; 250 | }; 251 | /* End PBXTargetDependency section */ 252 | 253 | /* Begin PBXVariantGroup section */ 254 | 39D667A91BE202AC00BF0BD7 /* LaunchScreen.storyboard */ = { 255 | isa = PBXVariantGroup; 256 | children = ( 257 | 39D667AA1BE202AC00BF0BD7 /* Base */, 258 | ); 259 | name = LaunchScreen.storyboard; 260 | sourceTree = ""; 261 | }; 262 | /* End PBXVariantGroup section */ 263 | 264 | /* Begin XCBuildConfiguration section */ 265 | 39D667AD1BE202AC00BF0BD7 /* Debug */ = { 266 | isa = XCBuildConfiguration; 267 | buildSettings = { 268 | ALWAYS_SEARCH_USER_PATHS = NO; 269 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 270 | CLANG_CXX_LIBRARY = "libc++"; 271 | CLANG_ENABLE_MODULES = YES; 272 | CLANG_ENABLE_OBJC_ARC = YES; 273 | CLANG_WARN_BOOL_CONVERSION = YES; 274 | CLANG_WARN_CONSTANT_CONVERSION = YES; 275 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 276 | CLANG_WARN_EMPTY_BODY = YES; 277 | CLANG_WARN_ENUM_CONVERSION = YES; 278 | CLANG_WARN_INFINITE_RECURSION = YES; 279 | CLANG_WARN_INT_CONVERSION = YES; 280 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 281 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 282 | CLANG_WARN_UNREACHABLE_CODE = YES; 283 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 284 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 285 | COPY_PHASE_STRIP = NO; 286 | DEBUG_INFORMATION_FORMAT = dwarf; 287 | ENABLE_STRICT_OBJC_MSGSEND = YES; 288 | ENABLE_TESTABILITY = YES; 289 | GCC_C_LANGUAGE_STANDARD = gnu99; 290 | GCC_DYNAMIC_NO_PIC = NO; 291 | GCC_NO_COMMON_BLOCKS = YES; 292 | GCC_OPTIMIZATION_LEVEL = 0; 293 | GCC_PREPROCESSOR_DEFINITIONS = ( 294 | "DEBUG=1", 295 | "$(inherited)", 296 | ); 297 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 298 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 299 | GCC_WARN_UNDECLARED_SELECTOR = YES; 300 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 301 | GCC_WARN_UNUSED_FUNCTION = YES; 302 | GCC_WARN_UNUSED_VARIABLE = YES; 303 | IPHONEOS_DEPLOYMENT_TARGET = 9.1; 304 | MTL_ENABLE_DEBUG_INFO = YES; 305 | ONLY_ACTIVE_ARCH = YES; 306 | SDKROOT = iphoneos; 307 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 308 | TARGETED_DEVICE_FAMILY = "1,2"; 309 | }; 310 | name = Debug; 311 | }; 312 | 39D667AE1BE202AC00BF0BD7 /* 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_INFINITE_RECURSION = YES; 326 | CLANG_WARN_INT_CONVERSION = YES; 327 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 328 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 329 | CLANG_WARN_UNREACHABLE_CODE = YES; 330 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 331 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 332 | COPY_PHASE_STRIP = NO; 333 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 334 | ENABLE_NS_ASSERTIONS = NO; 335 | ENABLE_STRICT_OBJC_MSGSEND = YES; 336 | GCC_C_LANGUAGE_STANDARD = gnu99; 337 | GCC_NO_COMMON_BLOCKS = YES; 338 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 339 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 340 | GCC_WARN_UNDECLARED_SELECTOR = YES; 341 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 342 | GCC_WARN_UNUSED_FUNCTION = YES; 343 | GCC_WARN_UNUSED_VARIABLE = YES; 344 | IPHONEOS_DEPLOYMENT_TARGET = 9.1; 345 | MTL_ENABLE_DEBUG_INFO = NO; 346 | SDKROOT = iphoneos; 347 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 348 | TARGETED_DEVICE_FAMILY = "1,2"; 349 | VALIDATE_PRODUCT = YES; 350 | }; 351 | name = Release; 352 | }; 353 | 39D667B01BE202AC00BF0BD7 /* Debug */ = { 354 | isa = XCBuildConfiguration; 355 | buildSettings = { 356 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 357 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 358 | CODE_SIGN_IDENTITY = "iPhone Developer"; 359 | DEVELOPMENT_TEAM = ""; 360 | INFOPLIST_FILE = Example/Info.plist; 361 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 362 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 363 | PRODUCT_BUNDLE_IDENTIFIER = com.startry.SwViewCapture.Example; 364 | PRODUCT_NAME = "$(TARGET_NAME)"; 365 | SWIFT_VERSION = 4.0; 366 | }; 367 | name = Debug; 368 | }; 369 | 39D667B11BE202AC00BF0BD7 /* Release */ = { 370 | isa = XCBuildConfiguration; 371 | buildSettings = { 372 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 373 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 374 | CODE_SIGN_IDENTITY = "iPhone Developer"; 375 | DEVELOPMENT_TEAM = ""; 376 | INFOPLIST_FILE = Example/Info.plist; 377 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 378 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 379 | PRODUCT_BUNDLE_IDENTIFIER = com.startry.SwViewCapture.Example; 380 | PRODUCT_NAME = "$(TARGET_NAME)"; 381 | SWIFT_VERSION = 4.0; 382 | }; 383 | name = Release; 384 | }; 385 | /* End XCBuildConfiguration section */ 386 | 387 | /* Begin XCConfigurationList section */ 388 | 39D667981BE202AC00BF0BD7 /* Build configuration list for PBXProject "Example" */ = { 389 | isa = XCConfigurationList; 390 | buildConfigurations = ( 391 | 39D667AD1BE202AC00BF0BD7 /* Debug */, 392 | 39D667AE1BE202AC00BF0BD7 /* Release */, 393 | ); 394 | defaultConfigurationIsVisible = 0; 395 | defaultConfigurationName = Release; 396 | }; 397 | 39D667AF1BE202AC00BF0BD7 /* Build configuration list for PBXNativeTarget "Example" */ = { 398 | isa = XCConfigurationList; 399 | buildConfigurations = ( 400 | 39D667B01BE202AC00BF0BD7 /* Debug */, 401 | 39D667B11BE202AC00BF0BD7 /* Release */, 402 | ); 403 | defaultConfigurationIsVisible = 0; 404 | defaultConfigurationName = Release; 405 | }; 406 | /* End XCConfigurationList section */ 407 | }; 408 | rootObject = 39D667951BE202AC00BF0BD7 /* Project object */; 409 | } 410 | -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "83.5x83.5", 66 | "scale" : "2x" 67 | } 68 | ], 69 | "info" : { 70 | "version" : 1, 71 | "author" : "xcode" 72 | } 73 | } -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/demo_1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "demo_1@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/demo_1.imageset/demo_1@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/startry/SwViewCapture/21f3db2998a03a8354b6b6b7f0e6790507349f0c/Example/Example/Assets.xcassets/demo_1.imageset/demo_1@2x.png -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/demo_2.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "demo_2@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /Example/Example/Assets.xcassets/demo_2.imageset/demo_2@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/startry/SwViewCapture/21f3db2998a03a8354b6b6b7f0e6790507349f0c/Example/Example/Assets.xcassets/demo_2.imageset/demo_2@2x.png -------------------------------------------------------------------------------- /Example/Example/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 | -------------------------------------------------------------------------------- /Example/Example/ImageViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageViewController.swift 3 | // Example 4 | // 5 | // Created by chenxing.cx on 16/2/17. 6 | // Copyright © 2016年 Startry. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ImageViewController: UIViewController { 12 | 13 | var scrollView: UIScrollView? 14 | var imageView: UIImageView? 15 | var image: UIImage? 16 | 17 | init(image: UIImage){ 18 | self.image = image 19 | super.init(nibName: nil, bundle: nil) 20 | } 21 | 22 | required init?(coder aDecoder: NSCoder) { 23 | super.init(coder: aDecoder) 24 | } 25 | 26 | override func viewDidLoad() { 27 | super.viewDidLoad() 28 | 29 | self.imageView = UIImageView() 30 | self.imageView?.image = image 31 | self.imageView?.contentMode = UIViewContentMode.scaleAspectFit 32 | 33 | 34 | self.scrollView = UIScrollView() 35 | 36 | self.scrollView?.addSubview(self.imageView!) 37 | self.view.addSubview(self.scrollView!) 38 | } 39 | 40 | override func viewDidAppear(_ animated: Bool) { 41 | super.viewDidAppear(animated) 42 | if image?.size.height == UIScreen.main.bounds.height { 43 | self.scrollView?.setContentOffset(CGPoint(x: 0, y: (self.scrollView?.contentSize.height)! - (self.scrollView?.frame.size.height)!), animated: true) 44 | } 45 | } 46 | 47 | override func viewDidLayoutSubviews() { 48 | super.viewDidLayoutSubviews() 49 | 50 | let height = (self.image?.size.height)! 51 | let width = (self.image?.size.width)! 52 | 53 | self.scrollView?.frame = self.view.bounds 54 | self.scrollView?.contentSize = CGSize(width: width, height: height) 55 | 56 | self.imageView?.frame = CGRect(x: 0, y: 0, width: width, height: height) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Example/Example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | NSPhotoLibraryUsageDescription 24 | 是否允许此App访问你的媒体资料库? 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | NSAllowsArbitraryLoads 30 | 31 | 32 | UILaunchStoryboardName 33 | LaunchScreen 34 | UIRequiredDeviceCapabilities 35 | 36 | armv7 37 | 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UISupportedInterfaceOrientations~ipad 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationPortraitUpsideDown 48 | UIInterfaceOrientationLandscapeLeft 49 | UIInterfaceOrientationLandscapeRight 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /Example/Example/STCollectionViewDemoController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // STCollectionViewDemoController.swift 3 | // Example 4 | // 5 | // Created by chenxing.cx on 2017/6/7. 6 | // Copyright © 2017年 Startry. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class STCollectionViewDemoController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource{ 12 | 13 | let cellIdentify = "reuseableCellIdentify" 14 | var collectView : UICollectionView? 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | 19 | navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Capture", style: UIBarButtonItemStyle.plain, target: self, action: #selector(STTableViewDemoController.didCaptureBtnClicked(_:))) 20 | 21 | let flowLayout = UICollectionViewFlowLayout() 22 | flowLayout.scrollDirection = UICollectionViewScrollDirection.vertical; 23 | collectView = UICollectionView(frame: CGRect.zero, collectionViewLayout: flowLayout) 24 | 25 | collectView?.dataSource = self 26 | collectView?.delegate = self 27 | 28 | collectView?.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellIdentify) 29 | 30 | view.addSubview(collectView!) 31 | } 32 | 33 | override func viewDidLayoutSubviews() { 34 | super.viewDidLayoutSubviews() 35 | collectView?.frame = view.bounds 36 | } 37 | 38 | // MARK: UICollectionView DataSource & Delegate 39 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 40 | return 500; 41 | } 42 | 43 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 44 | let cell = collectView?.dequeueReusableCell(withReuseIdentifier: cellIdentify, for: indexPath); 45 | 46 | if indexPath.row % 2 == 1 { 47 | cell?.contentView.backgroundColor = UIColor.orange 48 | }else { 49 | cell?.contentView.backgroundColor = UIColor.yellow 50 | } 51 | 52 | return cell!; 53 | } 54 | 55 | func numberOfSections(in collectionView: UICollectionView) -> Int { 56 | return 1; 57 | } 58 | 59 | // MARK : Capture Button Events 60 | @objc func didCaptureBtnClicked(_ button: UIButton){ 61 | 62 | collectView?.swContentCapture({ (capturedImage) -> Void in 63 | let vc = ImageViewController(image: capturedImage!) 64 | self.navigationController?.pushViewController(vc, animated: true) 65 | }) 66 | 67 | // tableView?.swContentScrollCapture({ (capturedImage) -> Void in 68 | // let vc = ImageViewController(image: capturedImage!) 69 | // self.navigationController?.pushViewController(vc, animated: true) 70 | // }) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Example/Example/STDemoAppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // STDemoAppDelegate.swift 3 | // Example 4 | // 5 | // Created by chenxing.cx on 15/10/29. 6 | // Copyright © 2015年 Startry. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class STDemoAppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | 20 | self.window = UIWindow(frame: UIScreen.main.bounds) 21 | 22 | let vc = STMenuDemoViewController() 23 | let navVc = UINavigationController(rootViewController: vc) 24 | 25 | self.window?.rootViewController = navVc 26 | self.window?.makeKeyAndVisible() 27 | 28 | return true 29 | } 30 | 31 | func applicationWillResignActive(_ application: UIApplication) { 32 | // 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. 33 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 34 | } 35 | 36 | func applicationDidEnterBackground(_ application: UIApplication) { 37 | // 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. 38 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 39 | } 40 | 41 | func applicationWillEnterForeground(_ application: UIApplication) { 42 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 43 | } 44 | 45 | func applicationDidBecomeActive(_ application: UIApplication) { 46 | // 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. 47 | } 48 | 49 | func applicationWillTerminate(_ application: UIApplication) { 50 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 51 | } 52 | 53 | 54 | } 55 | 56 | -------------------------------------------------------------------------------- /Example/Example/STMenuDemoViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // STMenuDemoViewController.swift 3 | // STViewCapture 4 | // 5 | // Created by chenxing.cx on 15/10/28. 6 | // Copyright © 2015年 startry.com All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class STMenuDemoViewController: UIViewController { 12 | 13 | fileprivate var viewBtn: UIButton? 14 | fileprivate var scrollViewBtn: UIButton? 15 | fileprivate var tableViewBtn: UIButton? 16 | fileprivate var webViewBtn: UIButton? 17 | fileprivate var oldWebViewBtn: UIButton? 18 | fileprivate var collectionBtn: UIButton? 19 | 20 | // MARK: Life Cycle 21 | 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | // Do any additional setup after loading the view, typically from a nib. 25 | initViews(); 26 | } 27 | 28 | override func didReceiveMemoryWarning() { 29 | super.didReceiveMemoryWarning() 30 | // Dispose of any resources that can be recreated. 31 | } 32 | 33 | // MARK: Private Methods 34 | 35 | fileprivate func initViews(){ 36 | view.backgroundColor = UIColor.white 37 | 38 | let viewBtn = UIButton() 39 | let scrollViewBtn = UIButton() 40 | let tableViewBtn = UIButton() 41 | let webViewBtn = UIButton() 42 | let oldWebViewBtn = UIButton() 43 | let collectBtn = UIButton() 44 | 45 | viewBtn.setTitleColor(UIColor.black, for: UIControlState.normal) 46 | scrollViewBtn.setTitleColor(UIColor.black, for: UIControlState.normal) 47 | tableViewBtn.setTitleColor(UIColor.black, for: UIControlState.normal) 48 | webViewBtn.setTitleColor(UIColor.black, for: UIControlState.normal) 49 | oldWebViewBtn.setTitleColor(UIColor.black, for: UIControlState.normal) 50 | collectBtn.setTitleColor(UIColor.black, for: UIControlState.normal) 51 | 52 | viewBtn.setTitle("View示例", for: UIControlState.normal) 53 | scrollViewBtn.setTitle("ScrollView示例", for: UIControlState.normal) 54 | tableViewBtn.setTitle("TableView示例", for: UIControlState.normal) 55 | webViewBtn.setTitle("WKWebView示例", for: UIControlState.normal) 56 | oldWebViewBtn.setTitle("UIWebView示例", for: UIControlState.normal) 57 | collectBtn.setTitle("CollectionView示例", for: UIControlState.normal) 58 | 59 | view.addSubview(viewBtn) 60 | view.addSubview(scrollViewBtn) 61 | view.addSubview(tableViewBtn) 62 | view.addSubview(webViewBtn) 63 | view.addSubview(oldWebViewBtn) 64 | view.addSubview(collectBtn) 65 | 66 | let actionSel = #selector(STMenuDemoViewController.didBtnClicked(_:)) 67 | 68 | viewBtn.addTarget(self, action: actionSel, for: UIControlEvents.touchUpInside) 69 | scrollViewBtn.addTarget(self, action: actionSel, for: UIControlEvents.touchUpInside) 70 | tableViewBtn.addTarget(self, action: actionSel, for: UIControlEvents.touchUpInside) 71 | webViewBtn.addTarget(self, action: actionSel, for: UIControlEvents.touchUpInside) 72 | oldWebViewBtn.addTarget(self, action: actionSel, for: UIControlEvents.touchUpInside) 73 | collectBtn.addTarget(self, action: actionSel, for: UIControlEvents.touchUpInside) 74 | 75 | self.viewBtn = viewBtn 76 | self.scrollViewBtn = scrollViewBtn 77 | self.tableViewBtn = tableViewBtn 78 | self.webViewBtn = webViewBtn 79 | self.oldWebViewBtn = oldWebViewBtn 80 | self.collectionBtn = collectBtn; 81 | } 82 | 83 | // MARK: Layout Views 84 | 85 | override func viewDidLayoutSubviews() { 86 | super.viewDidLayoutSubviews() 87 | 88 | let btnHeight:CGFloat = 30, btnWidth:CGFloat = 180.0, spanHeight:CGFloat = 20.0; 89 | let btnX = view.frame.size.width / 2 - btnWidth / 2; 90 | let midY = view.frame.size.height / 2; 91 | 92 | viewBtn?.frame = CGRect(x: btnX, y: midY - btnHeight * 3 - spanHeight * 2.5, width: btnWidth, height: btnHeight) 93 | scrollViewBtn?.frame = CGRect(x: btnX, y: midY - btnHeight * 2 - spanHeight * 1.5, width: btnWidth, height: btnHeight) 94 | tableViewBtn?.frame = CGRect(x: btnX, y: midY - btnHeight - spanHeight / 2, width: btnWidth, height: btnHeight) 95 | 96 | webViewBtn?.frame = CGRect(x: btnX, y: midY + spanHeight * 0.5, width: btnWidth, height: btnHeight) 97 | oldWebViewBtn?.frame = CGRect(x: btnX, y: midY + spanHeight * 1.5 + btnHeight, width: btnWidth, height: btnHeight) 98 | collectionBtn?.frame = CGRect(x: btnX, y: midY + spanHeight * 2.5 + btnHeight * 2, width: btnWidth, height: btnHeight) 99 | } 100 | 101 | // MARK: Events 102 | 103 | @objc func didBtnClicked(_ button: UIButton){ 104 | if(button == viewBtn) { 105 | let vc = STViewDemoController() 106 | navigationController?.pushViewController(vc, animated: true) 107 | }else if(button == scrollViewBtn) { 108 | let vc = STScrollViewDemoController() 109 | navigationController?.pushViewController(vc, animated: true) 110 | }else if(button == tableViewBtn) { 111 | let vc = STTableViewDemoController() 112 | navigationController?.pushViewController(vc, animated: true) 113 | }else if(button == webViewBtn) { 114 | let vc = STWKWebViewDemoController() 115 | // navigationController?.presentViewController(vc, animated: true, completion: nil) 116 | navigationController?.pushViewController(vc, animated: true) 117 | }else if(button == oldWebViewBtn) { 118 | let vc = STUIWebViewDemoController() 119 | navigationController?.pushViewController(vc, animated: true) 120 | }else if(button == collectionBtn) { 121 | let vc = STCollectionViewDemoController() 122 | navigationController?.pushViewController(vc, animated: true) 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /Example/Example/STScrollViewDemoController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // STScrollViewDemoController.swift 3 | // STViewCapture 4 | // 5 | // Created by chenxing.cx on 15/10/28. 6 | // Copyright © 2015年 startry.com All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import WebKit 11 | 12 | class STScrollViewDemoController: UIViewController { 13 | 14 | // MARK: Life Cycle 15 | 16 | var scrollView: UIScrollView? 17 | 18 | override func viewDidLoad() { 19 | super.viewDidLoad() 20 | 21 | navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Capture", style: UIBarButtonItemStyle.plain, target: self, action: #selector(STScrollViewDemoController.didCaptureBtnClicked(_:))) 22 | 23 | // Add Some Color View for Capture 24 | let orangeView = UIView(frame: CGRect(x: 30, y: 100, width: 100, height: 100)) 25 | let redView = UIView(frame: CGRect(x: 30, y: 200, width: 100, height: 100)) 26 | 27 | let headImage = UIImage(named: "demo_2") 28 | let headImageView = UIImageView(frame: CGRect(x: 30, y: 300, width: headImage!.size.width / 2, height: headImage!.size.height / 2)) 29 | headImageView.image = headImage 30 | 31 | let sceneImage = UIImage(named: "demo_1") 32 | let sceneImageView = UIImageView(frame: CGRect(x: 30, y: 500, width: sceneImage!.size.width / 2, height: sceneImage!.size.height / 2)) 33 | sceneImageView.image = sceneImage 34 | 35 | let url = URL(string: "http://www.startry.com") 36 | let request = URLRequest(url: url!) 37 | let webView = WKWebView(frame: CGRect(x: 0, y: 600, width: self.view.frame.size.width, height: 100)) 38 | webView.load(request) 39 | 40 | orangeView.backgroundColor = UIColor.orange 41 | redView.backgroundColor = UIColor.red 42 | 43 | scrollView = UIScrollView() 44 | scrollView?.backgroundColor = UIColor.orange 45 | scrollView?.contentSize = CGSize(width: view.bounds.width, height: 800) 46 | 47 | scrollView?.addSubview(orangeView) 48 | scrollView?.addSubview(redView) 49 | scrollView?.addSubview(headImageView) 50 | scrollView?.addSubview(sceneImageView) 51 | scrollView?.addSubview(webView) 52 | 53 | view.addSubview(scrollView!) 54 | } 55 | 56 | override func viewDidLayoutSubviews() { 57 | super.viewDidLayoutSubviews() 58 | 59 | scrollView?.frame = view.bounds 60 | } 61 | 62 | // MARK: Events 63 | 64 | @objc func didCaptureBtnClicked(_ button: UIButton){ 65 | 66 | scrollView?.swContentCapture({ (capturedImage) -> Void in 67 | 68 | UIImageWriteToSavedPhotosAlbum(capturedImage!, self, nil, nil) 69 | 70 | let vc = ImageViewController(image: capturedImage!) 71 | self.navigationController?.pushViewController(vc, animated: true) 72 | }) 73 | 74 | // scrollView?.swContentScrollCapture({ (capturedImage) -> Void in 75 | // 76 | // UIImageWriteToSavedPhotosAlbum(capturedImage!, self, nil, nil) 77 | // 78 | // let vc = ImageViewController(image: capturedImage!) 79 | // self.navigationController?.pushViewController(vc, animated: true) 80 | // }) 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /Example/Example/STTableViewDemoController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // STTableViewDemoController.swift 3 | // STViewCapture 4 | // 5 | // Created by chenxing.cx on 15/10/28. 6 | // Copyright © 2015年 startry.com All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class STTableViewDemoController: UIViewController, UITableViewDelegate, UITableViewDataSource { 12 | 13 | let cellIdentify = "resuseableCellIdentify" 14 | 15 | var tableView: UITableView? 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | 20 | navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Capture", style: UIBarButtonItemStyle.plain, target: self, action: #selector(STTableViewDemoController.didCaptureBtnClicked(_:))) 21 | 22 | tableView = UITableView() // tableView 23 | 24 | tableView?.dataSource = self 25 | tableView?.delegate = self 26 | 27 | tableView?.register(UITableViewCell.self, forCellReuseIdentifier: cellIdentify) 28 | 29 | view.addSubview(tableView!) 30 | } 31 | 32 | override func viewDidLayoutSubviews() { 33 | super.viewDidLayoutSubviews() 34 | tableView?.frame = view.bounds 35 | } 36 | 37 | // MARK: TableView DataSource 38 | 39 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 40 | return 100 41 | } 42 | 43 | func numberOfSections(in tableView: UITableView) -> Int { 44 | return 1 45 | } 46 | 47 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 48 | let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentify) 49 | cell?.textLabel?.text = "show cell \((indexPath as NSIndexPath).row)" 50 | 51 | return cell! 52 | } 53 | 54 | // MARK : Events 55 | @objc func didCaptureBtnClicked(_ button: UIButton){ 56 | 57 | tableView?.swContentCapture({ (capturedImage) -> Void in 58 | let vc = ImageViewController(image: capturedImage!) 59 | self.navigationController?.pushViewController(vc, animated: true) 60 | }) 61 | 62 | // tableView?.swContentScrollCapture({ (capturedImage) -> Void in 63 | // let vc = ImageViewController(image: capturedImage!) 64 | // self.navigationController?.pushViewController(vc, animated: true) 65 | // }) 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /Example/Example/STUIWebViewDemoController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // STUIWebViewDemoController.swift 3 | // Example 4 | // 5 | // Created by chenxing.cx on 16/2/15. 6 | // Copyright © 2016年 Startry. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class STUIWebViewDemoController: UIViewController { 12 | 13 | var webView: UIWebView? 14 | 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | 18 | view.backgroundColor = UIColor.red 19 | 20 | navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Capture", style: UIBarButtonItemStyle.plain, target: self, action: #selector(STUIWebViewDemoController.didCaptureBtnClicked(_:))) 21 | 22 | webView = UIWebView(frame: view.bounds) 23 | let url = URL(string: "http://www.startry.com") 24 | let request = URLRequest(url: url!) 25 | webView?.loadRequest(request) 26 | 27 | view.addSubview(webView!) 28 | } 29 | 30 | // MARK: Events 31 | @objc func didCaptureBtnClicked(_ button: UIButton){ 32 | webView?.swContentCapture({ (capturedImage) -> Void in 33 | 34 | UIImageWriteToSavedPhotosAlbum(capturedImage!, self, nil, nil) 35 | // 36 | // let vc = ImageViewController(image: capturedImage!) 37 | // self.navigationController?.pushViewController(vc, animated: true) 38 | }) 39 | 40 | // webView?.swContentScrollCapture({ (capturedImage) -> Void in 41 | // 42 | // UIImageWriteToSavedPhotosAlbum(capturedImage!, self, nil, nil) 43 | // 44 | // let vc = ImageViewController(image: capturedImage!) 45 | // self.navigationController?.pushViewController(vc, animated: true) 46 | // }) 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /Example/Example/STViewDemoController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // STViewDemoController.swift 3 | // STViewCapture 4 | // 5 | // Created by chenxing.cx on 10/28/2015. 6 | // Copyright (c) 2015 startry.com All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwViewCapture 11 | 12 | class STViewDemoController: UIViewController { 13 | 14 | // MARK: Life Cycle 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | 19 | view.backgroundColor = UIColor.yellow 20 | navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Capture", style: UIBarButtonItemStyle.plain, target: self, action: #selector(STViewDemoController.didCaptureBtnClicked(_:))) 21 | 22 | // Add Some Color View for Capture 23 | let orangeView = UIView(frame: CGRect(x: 100, y: 100, width: 20, height: 50)) 24 | let redView = UIView(frame: CGRect(x: 150, y: 200, width: 100, height: 100)) 25 | 26 | let headImage = UIImage(named: "demo_2") 27 | let headImageView = UIImageView(frame: CGRect(x: 30, y: 300, width: headImage!.size.width, height: headImage!.size.height)) 28 | headImageView.image = headImage 29 | 30 | orangeView.backgroundColor = UIColor.orange 31 | redView.backgroundColor = UIColor.red 32 | 33 | view.addSubview(orangeView) 34 | view.addSubview(redView) 35 | view.addSubview(headImageView) 36 | } 37 | 38 | // MARK: Events 39 | 40 | @objc func didCaptureBtnClicked(_ button: UIButton){ 41 | 42 | view.swCapture { (capturedImage) -> Void in 43 | let vc = ImageViewController(image: capturedImage!) 44 | self.navigationController?.pushViewController(vc, animated: true) 45 | } 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /Example/Example/STWKWebViewDemoController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // STWKWebViewDemoController.swift 3 | // STViewCapture 4 | // 5 | // Created by chenxing.cx on 15/10/28. 6 | // Copyright © 2015年 startry.com All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import WebKit 11 | 12 | class STWKWebViewDemoController: UIViewController { 13 | 14 | var webView: WKWebView? 15 | 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | 19 | view.backgroundColor = UIColor.red 20 | 21 | navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Capture", style: UIBarButtonItemStyle.plain, target: self, action: #selector(STWKWebViewDemoController.didCaptureBtnClicked(_:))) 22 | 23 | webView = WKWebView(frame: self.view.bounds) 24 | let url = URL(string: "http://www.startry.com") 25 | let request = URLRequest(url: url!) 26 | _ = webView?.load(request) 27 | 28 | view.addSubview(webView!) 29 | } 30 | 31 | // override func viewWillAppear(animated: Bool) { 32 | // self.navigationController?.setNavigationBarHidden(true, animated: animated) 33 | // } 34 | 35 | // override func viewDidAppear(animated: Bool) { 36 | // super.viewDidAppear(animated) 37 | // self.didCaptureBtnClicked(nil) 38 | // } 39 | 40 | override func viewDidLayoutSubviews() { 41 | super.viewDidLayoutSubviews() 42 | // webView?.frame = self.view.bounds 43 | } 44 | 45 | // MARK: Events 46 | @objc func didCaptureBtnClicked(_ button: UIButton?){ 47 | 48 | webView?.swContentCapture({ (capturedImage) -> Void in 49 | 50 | UIImageWriteToSavedPhotosAlbum(capturedImage!, self, nil, nil) 51 | 52 | let vc = ImageViewController(image: capturedImage!) 53 | self.navigationController?.pushViewController(vc, animated: true) 54 | }) 55 | 56 | // webView?.swContentScrollCapture({ (capturedImage) -> Void in 57 | // UIImageWriteToSavedPhotosAlbum(capturedImage!, self, nil, nil) 58 | // 59 | // let vc = ImageViewController(image: capturedImage!) 60 | // self.navigationController?.pushViewController(vc, animated: true) 61 | // }) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Xing Chen 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwViewCapture 2 | 3 | A nice iOS View Capture Library which can capture all content. 4 | 5 | SwViewCapture could convert all content of UIWebView to a UIImage. 6 | 7 | 一个用起来还不错的iOS截图库.(支持截取所有内容, 适用于所有ScrollView组成的视图, 包括WKWebView) 8 | 9 | SwViewCapture支持截取网页以及ScrollView的所有内容 10 | 11 | [![Version](https://img.shields.io/cocoapods/v/SwViewCapture.svg?style=flat)](http://cocoapods.org/pods/SwViewCapture) 12 | [![License](https://img.shields.io/cocoapods/l/SwViewCapture.svg?style=flat)](http://cocoapods.org/pods/SwViewCapture) 13 | [![Platform](https://img.shields.io/cocoapods/p/SwViewCapture.svg?style=flat)](http://cocoapods.org/pods/SwViewCapture) 14 | 15 | Example 16 | 17 | ## Feature 18 | 19 | 1. API is more easy to use. 20 | * use swift extension 21 | 2. Support to capture all content of scrollView. 22 | * eg: UIScrollView, UITableView, UIWebView 23 | 3. Support capture WKWebView. 24 | * WKWebview is hard to capture; 25 | * WKWebView could be capture like UIWebView 26 | 4. Flasing will not appear in the process of Screenshots. 27 | * SwCaptureView use a fake screenshots as a cover which over target view. All the action of target will be hidden below the fake screenshots. 28 | 29 | ###功能 30 | 31 | 1. API更容易使用. 32 | * 使用Extension去封装API 33 | 34 | 2. 支持截取滚动视图内的所有内容. 35 | * 支持UIScrollView, UITableView, UIWebView 36 | 37 | 3. 支持截取WKWebView的内容. 38 | * 因为WKWebView的内部实现问题, WKWebView比较难去截屏 39 | * 目前SwViewCapture对WKWebView的支持比较完美, 已经提供了两种截图方法, 非滚动的截图方式已经解决了`position: fixed`的问题 40 | 4. 截图过程中不会出现视图闪烁. 41 | * 截图过程中, 使用一张伪装截图遮盖屏幕, 底层截图活动不透明化。 42 | 43 | 44 | ## Usage 45 | 46 | * Capture basic screenshots (size of view's frame) 47 | 48 | ``` Swift 49 | import SwViewCapture 50 | // ... 51 | view.swCapture { (capturedImage) -> Void in 52 | // capturedImage is a UIImage. 53 | } 54 | ``` 55 | 56 | * Capture all content screenshots (size of content) 57 | 58 | ``` Swift 59 | import SwViewCapture 60 | // ... 61 | view.swContentCapture { (capturedImage) -> Void in 62 | // capturedImage is a UIImage. 63 | } 64 | ``` 65 | 66 | ###用法 67 | 68 | * 普通截屏(屏幕大小) 69 | 70 | ``` Swift 71 | import SwViewCapture 72 | // ... 73 | view.swCapture { (capturedImage) -> Void in 74 | // capturedImage is a UIImage. 75 | } 76 | ``` 77 | 78 | * 内容截屏(全部内容的大小) 79 | 80 | ``` Swift 81 | import SwViewCapture 82 | // ... 83 | view.swContentCapture { (capturedImage) -> Void in 84 | // capturedImage is a UIImage. 85 | } 86 | ``` 87 | 88 | * 滚动截屏 89 | 90 | ``` Swift 91 | import SwViewCapture 92 | // ... 93 | view.swContentScrollCapture { (capturedImage) -> Void in 94 | // capturedImage is a UIImage. 95 | } 96 | ``` 97 | 98 | ## Requirement 99 | 100 | iOS 8.0+, Swift 2.0+ or Swift 3.0(Compatiable) 101 | 102 | SwViewCapture is available through [CocoaPods](http://cocoapods.org) now. To Install it, simply and the following line to your Podfile: 103 | 104 | ``` ruby 105 | pod "SwViewCapture" 106 | ``` 107 | 108 | Or, if you’re using [Carthage](https://github.com/Carthage/Carthage), add SwViewCapture to your Cartfile: 109 | 110 | ``` 111 | github "startry/SwViewCapture" 112 | ``` 113 | 114 | ## License 115 | 116 | SwViewCapture is available under the MIT license. See the LICENSE file for more info. 117 | -------------------------------------------------------------------------------- /SwViewCapture.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "SwViewCapture" 3 | s.version = "1.0.6" 4 | s.summary = "A nice iOS View Capture Library which can capture all content." 5 | 6 | s.description = <<-DESC 7 | A nice iOS View Capture Library. SwViewCapture could capture all content of UIWebView & UIScrollView. 8 | DESC 9 | 10 | s.homepage = "https://github.com/startry/SwViewCapture" 11 | s.license = 'MIT' 12 | s.author = { "chenxing.cx" => "chenxingfl@gmail.com" } 13 | s.source = { :git => "https://github.com/startry/SwViewCapture.git", :tag => "1.0.5" } 14 | s.social_media_url = 'https://twitter.com/xStartry' 15 | 16 | s.platform = :ios, '8.0' 17 | s.requires_arc = true 18 | 19 | s.source_files = ["SwViewCapture/*.swift", "SwViewCapture/SwViewCapture.h"] 20 | s.public_header_files = ["SwViewCapture/SwViewCapture.h"] 21 | 22 | end 23 | -------------------------------------------------------------------------------- /SwViewCapture.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 394BA35F1C7451F60012FCB5 /* UIView+SwCapture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 394BA35E1C7451F60012FCB5 /* UIView+SwCapture.swift */; }; 11 | 39CC90871C785ED60057B0CE /* WKWebView+SwCapture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39CC90861C785ED60057B0CE /* WKWebView+SwCapture.swift */; }; 12 | 39CC90BF1C8033180057B0CE /* UIScrollView+SwCapture.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39CC90BE1C8033180057B0CE /* UIScrollView+SwCapture.swift */; }; 13 | 39D6678D1BE2024900BF0BD7 /* SwViewCapture.h in Headers */ = {isa = PBXBuildFile; fileRef = 39D6678C1BE2024900BF0BD7 /* SwViewCapture.h */; settings = {ATTRIBUTES = (Public, ); }; }; 14 | /* End PBXBuildFile section */ 15 | 16 | /* Begin PBXFileReference section */ 17 | 394BA35E1C7451F60012FCB5 /* UIView+SwCapture.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+SwCapture.swift"; sourceTree = ""; }; 18 | 39CC90861C785ED60057B0CE /* WKWebView+SwCapture.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "WKWebView+SwCapture.swift"; sourceTree = ""; }; 19 | 39CC90BE1C8033180057B0CE /* UIScrollView+SwCapture.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIScrollView+SwCapture.swift"; sourceTree = ""; }; 20 | 39D667891BE2024900BF0BD7 /* SwViewCapture.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwViewCapture.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 21 | 39D6678C1BE2024900BF0BD7 /* SwViewCapture.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwViewCapture.h; sourceTree = ""; }; 22 | 39D6678E1BE2024900BF0BD7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 23 | /* End PBXFileReference section */ 24 | 25 | /* Begin PBXFrameworksBuildPhase section */ 26 | 39D667851BE2024900BF0BD7 /* Frameworks */ = { 27 | isa = PBXFrameworksBuildPhase; 28 | buildActionMask = 2147483647; 29 | files = ( 30 | ); 31 | runOnlyForDeploymentPostprocessing = 0; 32 | }; 33 | /* End PBXFrameworksBuildPhase section */ 34 | 35 | /* Begin PBXGroup section */ 36 | 39D6677F1BE2024900BF0BD7 = { 37 | isa = PBXGroup; 38 | children = ( 39 | 39D6678B1BE2024900BF0BD7 /* SwViewCapture */, 40 | 39D6678A1BE2024900BF0BD7 /* Products */, 41 | ); 42 | sourceTree = ""; 43 | }; 44 | 39D6678A1BE2024900BF0BD7 /* Products */ = { 45 | isa = PBXGroup; 46 | children = ( 47 | 39D667891BE2024900BF0BD7 /* SwViewCapture.framework */, 48 | ); 49 | name = Products; 50 | sourceTree = ""; 51 | }; 52 | 39D6678B1BE2024900BF0BD7 /* SwViewCapture */ = { 53 | isa = PBXGroup; 54 | children = ( 55 | 39D6678C1BE2024900BF0BD7 /* SwViewCapture.h */, 56 | 39D6678E1BE2024900BF0BD7 /* Info.plist */, 57 | 394BA35E1C7451F60012FCB5 /* UIView+SwCapture.swift */, 58 | 39CC90861C785ED60057B0CE /* WKWebView+SwCapture.swift */, 59 | 39CC90BE1C8033180057B0CE /* UIScrollView+SwCapture.swift */, 60 | ); 61 | path = SwViewCapture; 62 | sourceTree = ""; 63 | }; 64 | /* End PBXGroup section */ 65 | 66 | /* Begin PBXHeadersBuildPhase section */ 67 | 39D667861BE2024900BF0BD7 /* Headers */ = { 68 | isa = PBXHeadersBuildPhase; 69 | buildActionMask = 2147483647; 70 | files = ( 71 | 39D6678D1BE2024900BF0BD7 /* SwViewCapture.h in Headers */, 72 | ); 73 | runOnlyForDeploymentPostprocessing = 0; 74 | }; 75 | /* End PBXHeadersBuildPhase section */ 76 | 77 | /* Begin PBXNativeTarget section */ 78 | 39D667881BE2024900BF0BD7 /* SwViewCapture */ = { 79 | isa = PBXNativeTarget; 80 | buildConfigurationList = 39D667911BE2024900BF0BD7 /* Build configuration list for PBXNativeTarget "SwViewCapture" */; 81 | buildPhases = ( 82 | 39D667841BE2024900BF0BD7 /* Sources */, 83 | 39D667851BE2024900BF0BD7 /* Frameworks */, 84 | 39D667861BE2024900BF0BD7 /* Headers */, 85 | 39D667871BE2024900BF0BD7 /* Resources */, 86 | ); 87 | buildRules = ( 88 | ); 89 | dependencies = ( 90 | ); 91 | name = SwViewCapture; 92 | productName = SwViewCapture; 93 | productReference = 39D667891BE2024900BF0BD7 /* SwViewCapture.framework */; 94 | productType = "com.apple.product-type.framework"; 95 | }; 96 | /* End PBXNativeTarget section */ 97 | 98 | /* Begin PBXProject section */ 99 | 39D667801BE2024900BF0BD7 /* Project object */ = { 100 | isa = PBXProject; 101 | attributes = { 102 | LastUpgradeCheck = 0800; 103 | ORGANIZATIONNAME = Startry; 104 | TargetAttributes = { 105 | 39D667881BE2024900BF0BD7 = { 106 | CreatedOnToolsVersion = 7.1; 107 | LastSwiftMigration = 0800; 108 | }; 109 | }; 110 | }; 111 | buildConfigurationList = 39D667831BE2024900BF0BD7 /* Build configuration list for PBXProject "SwViewCapture" */; 112 | compatibilityVersion = "Xcode 3.2"; 113 | developmentRegion = English; 114 | hasScannedForEncodings = 0; 115 | knownRegions = ( 116 | en, 117 | ); 118 | mainGroup = 39D6677F1BE2024900BF0BD7; 119 | productRefGroup = 39D6678A1BE2024900BF0BD7 /* Products */; 120 | projectDirPath = ""; 121 | projectRoot = ""; 122 | targets = ( 123 | 39D667881BE2024900BF0BD7 /* SwViewCapture */, 124 | ); 125 | }; 126 | /* End PBXProject section */ 127 | 128 | /* Begin PBXResourcesBuildPhase section */ 129 | 39D667871BE2024900BF0BD7 /* Resources */ = { 130 | isa = PBXResourcesBuildPhase; 131 | buildActionMask = 2147483647; 132 | files = ( 133 | ); 134 | runOnlyForDeploymentPostprocessing = 0; 135 | }; 136 | /* End PBXResourcesBuildPhase section */ 137 | 138 | /* Begin PBXSourcesBuildPhase section */ 139 | 39D667841BE2024900BF0BD7 /* Sources */ = { 140 | isa = PBXSourcesBuildPhase; 141 | buildActionMask = 2147483647; 142 | files = ( 143 | 39CC90871C785ED60057B0CE /* WKWebView+SwCapture.swift in Sources */, 144 | 394BA35F1C7451F60012FCB5 /* UIView+SwCapture.swift in Sources */, 145 | 39CC90BF1C8033180057B0CE /* UIScrollView+SwCapture.swift in Sources */, 146 | ); 147 | runOnlyForDeploymentPostprocessing = 0; 148 | }; 149 | /* End PBXSourcesBuildPhase section */ 150 | 151 | /* Begin XCBuildConfiguration section */ 152 | 39D6678F1BE2024900BF0BD7 /* Debug */ = { 153 | isa = XCBuildConfiguration; 154 | buildSettings = { 155 | ALWAYS_SEARCH_USER_PATHS = NO; 156 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 157 | CLANG_CXX_LIBRARY = "libc++"; 158 | CLANG_ENABLE_MODULES = YES; 159 | CLANG_ENABLE_OBJC_ARC = YES; 160 | CLANG_WARN_BOOL_CONVERSION = YES; 161 | CLANG_WARN_CONSTANT_CONVERSION = YES; 162 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 163 | CLANG_WARN_EMPTY_BODY = YES; 164 | CLANG_WARN_ENUM_CONVERSION = YES; 165 | CLANG_WARN_INFINITE_RECURSION = YES; 166 | CLANG_WARN_INT_CONVERSION = YES; 167 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 168 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 169 | CLANG_WARN_UNREACHABLE_CODE = YES; 170 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 171 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 172 | COPY_PHASE_STRIP = NO; 173 | CURRENT_PROJECT_VERSION = 1; 174 | DEBUG_INFORMATION_FORMAT = dwarf; 175 | ENABLE_STRICT_OBJC_MSGSEND = YES; 176 | ENABLE_TESTABILITY = YES; 177 | GCC_C_LANGUAGE_STANDARD = gnu99; 178 | GCC_DYNAMIC_NO_PIC = NO; 179 | GCC_NO_COMMON_BLOCKS = YES; 180 | GCC_OPTIMIZATION_LEVEL = 0; 181 | GCC_PREPROCESSOR_DEFINITIONS = ( 182 | "DEBUG=1", 183 | "$(inherited)", 184 | ); 185 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 186 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 187 | GCC_WARN_UNDECLARED_SELECTOR = YES; 188 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 189 | GCC_WARN_UNUSED_FUNCTION = YES; 190 | GCC_WARN_UNUSED_VARIABLE = YES; 191 | IPHONEOS_DEPLOYMENT_TARGET = 9.1; 192 | MTL_ENABLE_DEBUG_INFO = YES; 193 | ONLY_ACTIVE_ARCH = YES; 194 | SDKROOT = iphoneos; 195 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 196 | TARGETED_DEVICE_FAMILY = "1,2"; 197 | VERSIONING_SYSTEM = "apple-generic"; 198 | VERSION_INFO_PREFIX = ""; 199 | }; 200 | name = Debug; 201 | }; 202 | 39D667901BE2024900BF0BD7 /* Release */ = { 203 | isa = XCBuildConfiguration; 204 | buildSettings = { 205 | ALWAYS_SEARCH_USER_PATHS = NO; 206 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 207 | CLANG_CXX_LIBRARY = "libc++"; 208 | CLANG_ENABLE_MODULES = YES; 209 | CLANG_ENABLE_OBJC_ARC = YES; 210 | CLANG_WARN_BOOL_CONVERSION = YES; 211 | CLANG_WARN_CONSTANT_CONVERSION = YES; 212 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 213 | CLANG_WARN_EMPTY_BODY = YES; 214 | CLANG_WARN_ENUM_CONVERSION = YES; 215 | CLANG_WARN_INFINITE_RECURSION = YES; 216 | CLANG_WARN_INT_CONVERSION = YES; 217 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 218 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 219 | CLANG_WARN_UNREACHABLE_CODE = YES; 220 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 221 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 222 | COPY_PHASE_STRIP = NO; 223 | CURRENT_PROJECT_VERSION = 1; 224 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 225 | ENABLE_NS_ASSERTIONS = NO; 226 | ENABLE_STRICT_OBJC_MSGSEND = YES; 227 | GCC_C_LANGUAGE_STANDARD = gnu99; 228 | GCC_NO_COMMON_BLOCKS = YES; 229 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 230 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 231 | GCC_WARN_UNDECLARED_SELECTOR = YES; 232 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 233 | GCC_WARN_UNUSED_FUNCTION = YES; 234 | GCC_WARN_UNUSED_VARIABLE = YES; 235 | IPHONEOS_DEPLOYMENT_TARGET = 9.1; 236 | MTL_ENABLE_DEBUG_INFO = NO; 237 | SDKROOT = iphoneos; 238 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 239 | TARGETED_DEVICE_FAMILY = "1,2"; 240 | VALIDATE_PRODUCT = YES; 241 | VERSIONING_SYSTEM = "apple-generic"; 242 | VERSION_INFO_PREFIX = ""; 243 | }; 244 | name = Release; 245 | }; 246 | 39D667921BE2024900BF0BD7 /* Debug */ = { 247 | isa = XCBuildConfiguration; 248 | buildSettings = { 249 | CLANG_ENABLE_MODULES = YES; 250 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 251 | DEFINES_MODULE = YES; 252 | DYLIB_COMPATIBILITY_VERSION = 1; 253 | DYLIB_CURRENT_VERSION = 1; 254 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 255 | INFOPLIST_FILE = SwViewCapture/Info.plist; 256 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 257 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 258 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 259 | ONLY_ACTIVE_ARCH = YES; 260 | PRODUCT_BUNDLE_IDENTIFIER = com.startry.SwViewCapture; 261 | PRODUCT_NAME = "$(TARGET_NAME)"; 262 | SDKROOT = iphoneos; 263 | SKIP_INSTALL = YES; 264 | SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; 265 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 266 | SWIFT_VERSION = 4.0; 267 | }; 268 | name = Debug; 269 | }; 270 | 39D667931BE2024900BF0BD7 /* Release */ = { 271 | isa = XCBuildConfiguration; 272 | buildSettings = { 273 | CLANG_ENABLE_MODULES = YES; 274 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 275 | DEFINES_MODULE = YES; 276 | DYLIB_COMPATIBILITY_VERSION = 1; 277 | DYLIB_CURRENT_VERSION = 1; 278 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 279 | INFOPLIST_FILE = SwViewCapture/Info.plist; 280 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 281 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 282 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 283 | PRODUCT_BUNDLE_IDENTIFIER = com.startry.SwViewCapture; 284 | PRODUCT_NAME = "$(TARGET_NAME)"; 285 | SDKROOT = iphoneos; 286 | SKIP_INSTALL = YES; 287 | SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; 288 | SWIFT_VERSION = 4.0; 289 | }; 290 | name = Release; 291 | }; 292 | /* End XCBuildConfiguration section */ 293 | 294 | /* Begin XCConfigurationList section */ 295 | 39D667831BE2024900BF0BD7 /* Build configuration list for PBXProject "SwViewCapture" */ = { 296 | isa = XCConfigurationList; 297 | buildConfigurations = ( 298 | 39D6678F1BE2024900BF0BD7 /* Debug */, 299 | 39D667901BE2024900BF0BD7 /* Release */, 300 | ); 301 | defaultConfigurationIsVisible = 0; 302 | defaultConfigurationName = Release; 303 | }; 304 | 39D667911BE2024900BF0BD7 /* Build configuration list for PBXNativeTarget "SwViewCapture" */ = { 305 | isa = XCConfigurationList; 306 | buildConfigurations = ( 307 | 39D667921BE2024900BF0BD7 /* Debug */, 308 | 39D667931BE2024900BF0BD7 /* Release */, 309 | ); 310 | defaultConfigurationIsVisible = 0; 311 | defaultConfigurationName = Release; 312 | }; 313 | /* End XCConfigurationList section */ 314 | }; 315 | rootObject = 39D667801BE2024900BF0BD7 /* Project object */; 316 | } 317 | -------------------------------------------------------------------------------- /SwViewCapture.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwViewCapture.xcodeproj/xcshareddata/xcschemes/SwViewCapture.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /SwViewCapture.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /SwViewCapture/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 | -------------------------------------------------------------------------------- /SwViewCapture/SwViewCapture.h: -------------------------------------------------------------------------------- 1 | // 2 | // SwViewCapture.h 3 | // SwViewCapture 4 | // 5 | // Created by chenxing.cx on 15/10/29. 6 | // Copyright © 2015年 Startry. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | //! Project version number for SwViewCapture. 12 | FOUNDATION_EXPORT double SwViewCaptureVersionNumber; 13 | 14 | //! Project version string for SwViewCapture. 15 | FOUNDATION_EXPORT const unsigned char SwViewCaptureVersionString[]; 16 | 17 | // In this header, you should import all the public headers of your framework using statements like #import 18 | 19 | 20 | -------------------------------------------------------------------------------- /SwViewCapture/UIScrollView+SwCapture.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIScrollView+SwCapture.swift 3 | // SwViewCapture 4 | // 5 | // Created by chenxing.cx on 16/2/26. 6 | // Copyright © 2016年 Startry. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import WebKit 11 | 12 | public extension UIScrollView { 13 | 14 | public func swContentCapture (_ completionHandler: @escaping (_ capturedImage: UIImage?) -> Void) { 15 | 16 | self.isCapturing = true 17 | 18 | // Put a fake Cover of View 19 | let snapShotView = self.snapshotView(afterScreenUpdates: false) 20 | snapShotView?.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: (snapShotView?.frame.size.width)!, height: (snapShotView?.frame.size.height)!) 21 | self.superview?.addSubview(snapShotView!) 22 | 23 | // Backup all properties of scrollview if needed 24 | let bakFrame = self.frame 25 | let bakOffset = self.contentOffset 26 | let bakSuperView = self.superview 27 | let bakIndex = self.superview?.subviews.index(of: self) 28 | 29 | // Scroll To Bottom show all cached view 30 | if self.frame.size.height < self.contentSize.height { 31 | self.contentOffset = CGPoint(x: 0, y: self.contentSize.height - self.frame.size.height) 32 | } 33 | 34 | self.swRenderImageView({ [weak self] (capturedImage) -> Void in 35 | // Recover View 36 | 37 | let strongSelf = self! 38 | 39 | strongSelf.removeFromSuperview() 40 | strongSelf.frame = bakFrame 41 | strongSelf.contentOffset = bakOffset 42 | bakSuperView?.insertSubview(strongSelf, at: bakIndex!) 43 | 44 | snapShotView?.removeFromSuperview() 45 | 46 | strongSelf.isCapturing = false 47 | 48 | completionHandler(capturedImage) 49 | }) 50 | 51 | } 52 | 53 | fileprivate func swRenderImageView(_ completionHandler: @escaping (_ capturedImage: UIImage?) -> Void) { 54 | // Rebuild scrollView superView and their hold relationship 55 | let swTempRenderView = UIView(frame: CGRect(x: 0, y: 0, width: self.contentSize.width, height: self.contentSize.height)) 56 | self.removeFromSuperview() 57 | swTempRenderView.addSubview(self) 58 | 59 | self.contentOffset = CGPoint.zero 60 | self.frame = swTempRenderView.bounds 61 | 62 | // Swizzling setFrame 63 | let method: Method = class_getInstanceMethod(object_getClass(self), #selector(setter: UIView.frame))! 64 | let swizzledMethod: Method = class_getInstanceMethod(object_getClass(self), Selector(("swSetFrame:")))! 65 | method_exchangeImplementations(method, swizzledMethod) 66 | 67 | // Sometimes ScrollView will Capture nothing without defer; 68 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(0.3 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)) { () -> Void in 69 | let bounds = self.bounds 70 | UIGraphicsBeginImageContextWithOptions(bounds.size, false, UIScreen.main.scale) 71 | 72 | if (self.swContainsWKWebView()) { 73 | self.drawHierarchy(in: bounds, afterScreenUpdates: true) 74 | }else{ 75 | self.layer.render(in: UIGraphicsGetCurrentContext()!) 76 | } 77 | let capturedImage = UIGraphicsGetImageFromCurrentImageContext() 78 | UIGraphicsEndImageContext() 79 | 80 | method_exchangeImplementations(swizzledMethod, method) 81 | 82 | completionHandler(capturedImage) 83 | } 84 | } 85 | 86 | 87 | // Simulate People Action, all the `fixed` element will be repeate 88 | // SwContentCapture will capture all content without simulate people action, more perfect. 89 | public func swContentScrollCapture (_ completionHandler: @escaping (_ capturedImage: UIImage?) -> Void) { 90 | 91 | self.isCapturing = true 92 | 93 | // Put a fake Cover of View 94 | let snapShotView = self.snapshotView(afterScreenUpdates: true) 95 | snapShotView?.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: (snapShotView?.frame.size.width)!, height: (snapShotView?.frame.size.height)!) 96 | self.superview?.addSubview(snapShotView!) 97 | 98 | // Backup 99 | let bakOffset = self.contentOffset 100 | 101 | // Divide 102 | let page = floorf(Float(self.contentSize.height / self.bounds.height)) 103 | 104 | UIGraphicsBeginImageContextWithOptions(self.contentSize, false, UIScreen.main.scale) 105 | 106 | self.swContentScrollPageDraw(0, maxIndex: Int(page), drawCallback: { [weak self] () -> Void in 107 | let strongSelf = self 108 | 109 | let capturedImage = UIGraphicsGetImageFromCurrentImageContext() 110 | UIGraphicsEndImageContext() 111 | 112 | // Recover 113 | strongSelf?.setContentOffset(bakOffset, animated: false) 114 | snapShotView?.removeFromSuperview() 115 | 116 | strongSelf?.isCapturing = false 117 | 118 | completionHandler(capturedImage) 119 | }) 120 | 121 | } 122 | 123 | fileprivate func swContentScrollPageDraw (_ index: Int, maxIndex: Int, drawCallback: @escaping () -> Void) { 124 | 125 | self.setContentOffset(CGPoint(x: 0, y: CGFloat(index) * self.frame.size.height), animated: false) 126 | let splitFrame = CGRect(x: 0, y: CGFloat(index) * self.frame.size.height, width: bounds.size.width, height: bounds.size.height) 127 | 128 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(0.3 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)) { () -> Void in 129 | self.drawHierarchy(in: splitFrame, afterScreenUpdates: true) 130 | 131 | if index < maxIndex { 132 | self.swContentScrollPageDraw(index + 1, maxIndex: maxIndex, drawCallback: drawCallback) 133 | }else{ 134 | drawCallback() 135 | } 136 | } 137 | } 138 | } 139 | 140 | public extension UIWebView { 141 | 142 | public func swContentCapture (_ completionHandler: @escaping (_ capturedImage: UIImage?) -> Void) { 143 | self.scrollView.swContentCapture(completionHandler) 144 | } 145 | 146 | public func swContentScrollCapture (_ completionHandler: @escaping (_ capturedImage: UIImage?) -> Void) { 147 | self.scrollView.swContentScrollCapture(completionHandler) 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /SwViewCapture/UIView+SwCapture.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIView+SwCapture.swift 3 | // SwViewCapture 4 | // 5 | // Created by chenxing.cx on 16/2/17. 6 | // Copyright © 2016年 Startry. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import WebKit 11 | import ObjectiveC 12 | 13 | private var SwViewCaptureKey_IsCapturing: String = "SwViewCapture_AssoKey_isCapturing" 14 | 15 | public extension UIView { 16 | 17 | @objc public func swSetFrame(_ frame: CGRect) { 18 | // Do nothing, use for swizzling 19 | } 20 | 21 | var isCapturing:Bool! { 22 | get { 23 | let num = objc_getAssociatedObject(self, &SwViewCaptureKey_IsCapturing) 24 | if num == nil { 25 | return false 26 | } 27 | 28 | if let numObj = num as? NSNumber { 29 | return numObj.boolValue 30 | }else { 31 | return false 32 | } 33 | } 34 | set(newValue) { 35 | let num = NSNumber(value: newValue as Bool) 36 | objc_setAssociatedObject(self, &SwViewCaptureKey_IsCapturing, num, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC) 37 | } 38 | } 39 | 40 | // Ref: chromium source - snapshot_manager, fix wkwebview screenshot bug. 41 | // https://chromium.googlesource.com/chromium/src.git/+/46.0.2478.0/ios/chrome/browser/snapshots/snapshot_manager.mm 42 | public func swContainsWKWebView() -> Bool { 43 | if self.isKind(of: WKWebView.self) { 44 | return true 45 | } 46 | for subView in self.subviews { 47 | if (subView.swContainsWKWebView()) { 48 | return true 49 | } 50 | } 51 | return false 52 | } 53 | 54 | public func swCapture(_ completionHandler: (_ capturedImage: UIImage?) -> Void) { 55 | 56 | self.isCapturing = true 57 | 58 | let bounds = self.bounds 59 | 60 | UIGraphicsBeginImageContextWithOptions(bounds.size, false, UIScreen.main.scale) 61 | 62 | let context = UIGraphicsGetCurrentContext() 63 | context?.saveGState() 64 | context?.translateBy(x: -self.frame.origin.x, y: -self.frame.origin.y); 65 | 66 | if (swContainsWKWebView()) { 67 | self.drawHierarchy(in: bounds, afterScreenUpdates: true) 68 | }else{ 69 | self.layer.render(in: context!) 70 | } 71 | let capturedImage = UIGraphicsGetImageFromCurrentImageContext() 72 | 73 | context?.restoreGState(); 74 | UIGraphicsEndImageContext() 75 | 76 | self.isCapturing = false 77 | 78 | completionHandler(capturedImage) 79 | } 80 | } 81 | 82 | -------------------------------------------------------------------------------- /SwViewCapture/WKWebView+SwCapture.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WKWebView+SwCapture.swift 3 | // SwViewCapture 4 | // 5 | // Created by chenxing.cx on 16/2/19. 6 | // Copyright © 2016年 Startry. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import WebKit 11 | import ObjectiveC 12 | 13 | public extension WKWebView { 14 | 15 | public func swContentCapture(_ completionHandler:@escaping (_ capturedImage: UIImage?) -> Void) { 16 | 17 | self.isCapturing = true 18 | 19 | let offset = self.scrollView.contentOffset 20 | 21 | // Put a fake Cover of View 22 | let snapShotView = self.snapshotView(afterScreenUpdates: true) 23 | snapShotView?.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: (snapShotView?.frame.size.width)!, height: (snapShotView?.frame.size.height)!) 24 | self.superview?.addSubview(snapShotView!) 25 | 26 | if self.frame.size.height < self.scrollView.contentSize.height { 27 | self.scrollView.contentOffset = CGPoint(x: 0, y: self.scrollView.contentSize.height - self.frame.size.height) 28 | } 29 | 30 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(0.3 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)) { () -> Void in 31 | self.scrollView.contentOffset = CGPoint.zero 32 | 33 | self.swContentCaptureWithoutOffset({ [weak self] (capturedImage) -> Void in 34 | let strongSelf = self! 35 | 36 | strongSelf.scrollView.contentOffset = offset 37 | 38 | snapShotView?.removeFromSuperview() 39 | 40 | strongSelf.isCapturing = false 41 | 42 | completionHandler(capturedImage) 43 | }) 44 | } 45 | } 46 | 47 | fileprivate func swContentCaptureWithoutOffset(_ completionHandler:@escaping (_ capturedImage: UIImage?) -> Void) { 48 | let containerView = UIView(frame: self.bounds) 49 | 50 | let bakFrame = self.frame 51 | let bakSuperView = self.superview 52 | let bakIndex = self.superview?.subviews.index(of: self) 53 | 54 | // remove WebView from superview & put container view 55 | self.removeFromSuperview() 56 | containerView.addSubview(self) 57 | 58 | let totalSize = self.scrollView.contentSize 59 | 60 | // Divide 61 | let page = floorf(Float( totalSize.height / containerView.bounds.height)) 62 | 63 | self.frame = CGRect(x: 0, y: 0, width: containerView.bounds.size.width, height: self.scrollView.contentSize.height) 64 | 65 | UIGraphicsBeginImageContextWithOptions(totalSize, false, UIScreen.main.scale) 66 | 67 | self.swContentPageDraw(containerView, index: 0, maxIndex: Int(page), drawCallback: { [weak self] () -> Void in 68 | let strongSelf = self! 69 | 70 | let capturedImage = UIGraphicsGetImageFromCurrentImageContext() 71 | UIGraphicsEndImageContext() 72 | 73 | // Recover 74 | strongSelf.removeFromSuperview() 75 | bakSuperView?.insertSubview(strongSelf, at: bakIndex!) 76 | 77 | strongSelf.frame = bakFrame 78 | 79 | containerView.removeFromSuperview() 80 | 81 | completionHandler(capturedImage) 82 | }) 83 | } 84 | 85 | fileprivate func swContentPageDraw (_ targetView: UIView, index: Int, maxIndex: Int, drawCallback: @escaping () -> Void) { 86 | 87 | // set up split frame of super view 88 | let splitFrame = CGRect(x: 0, y: CGFloat(index) * targetView.frame.size.height, width: targetView.bounds.size.width, height: targetView.frame.size.height) 89 | // set up webview frame 90 | var myFrame = self.frame 91 | myFrame.origin.y = -(CGFloat(index) * targetView.frame.size.height) 92 | self.frame = myFrame 93 | 94 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(0.3 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)) { () -> Void in 95 | targetView.drawHierarchy(in: splitFrame, afterScreenUpdates: true) 96 | 97 | if index < maxIndex { 98 | self.swContentPageDraw(targetView, index: index + 1, maxIndex: maxIndex, drawCallback: drawCallback) 99 | }else{ 100 | drawCallback() 101 | } 102 | } 103 | } 104 | 105 | 106 | // Simulate People Action, all the `fixed` element will be repeate 107 | // SwContentCapture will capture all content without simulate people action, more perfect. 108 | public func swContentScrollCapture (_ completionHandler: @escaping (_ capturedImage: UIImage?) -> Void) { 109 | 110 | self.isCapturing = true 111 | 112 | // Put a fake Cover of View 113 | let snapShotView = self.snapshotView(afterScreenUpdates: true) 114 | snapShotView?.frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: (snapShotView?.frame.size.width)!, height: (snapShotView?.frame.size.height)!) 115 | self.superview?.addSubview(snapShotView!) 116 | 117 | // Backup 118 | let bakOffset = self.scrollView.contentOffset 119 | 120 | // Divide 121 | let page = floorf(Float(self.scrollView.contentSize.height / self.bounds.height)) 122 | 123 | UIGraphicsBeginImageContextWithOptions(self.scrollView.contentSize, false, UIScreen.main.scale) 124 | 125 | self.swContentScrollPageDraw(0, maxIndex: Int(page), drawCallback: { [weak self] () -> Void in 126 | let strongSelf = self 127 | 128 | let capturedImage = UIGraphicsGetImageFromCurrentImageContext() 129 | UIGraphicsEndImageContext() 130 | 131 | // Recover 132 | strongSelf?.scrollView.setContentOffset(bakOffset, animated: false) 133 | snapShotView?.removeFromSuperview() 134 | 135 | strongSelf?.isCapturing = false 136 | 137 | completionHandler(capturedImage) 138 | }) 139 | 140 | } 141 | 142 | fileprivate func swContentScrollPageDraw (_ index: Int, maxIndex: Int, drawCallback: @escaping () -> Void) { 143 | 144 | self.scrollView.setContentOffset(CGPoint(x: 0, y: CGFloat(index) * self.scrollView.frame.size.height), animated: false) 145 | let splitFrame = CGRect(x: 0, y: CGFloat(index) * self.scrollView.frame.size.height, width: bounds.size.width, height: bounds.size.height) 146 | 147 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Double(Int64(0.3 * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)) { () -> Void in 148 | self.drawHierarchy(in: splitFrame, afterScreenUpdates: true) 149 | 150 | if index < maxIndex { 151 | self.swContentScrollPageDraw(index + 1, maxIndex: maxIndex, drawCallback: drawCallback) 152 | }else{ 153 | drawCallback() 154 | } 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /capture_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/startry/SwViewCapture/21f3db2998a03a8354b6b6b7f0e6790507349f0c/capture_demo.gif --------------------------------------------------------------------------------