├── .gitignore ├── HTMLWithImagesToPDF.playground ├── Contents.swift ├── Resources │ ├── apple.png │ └── index.html └── contents.xcplayground ├── HTMLWithImagesToPDF.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── HTMLWithImagesToPDF ├── Source │ ├── AppDelegate.swift │ ├── Extensions │ │ └── UIAlertController.swift │ ├── HTML.swift │ ├── PDF.swift │ └── ViewController.swift ├── Storyboards │ ├── LaunchScreen.storyboard │ └── Main.storyboard └── Supporting Files │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ └── apple.imageset │ │ ├── Contents.json │ │ └── apple.png │ ├── Info.plist │ ├── apple.png │ ├── index-img.html │ └── index.html ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | # Xcode 4 | # 5 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 6 | 7 | ## Build generated 8 | build/ 9 | DerivedData/ 10 | 11 | ## Various settings 12 | *.pbxuser 13 | !default.pbxuser 14 | *.mode1v3 15 | !default.mode1v3 16 | *.mode2v3 17 | !default.mode2v3 18 | *.perspectivev3 19 | !default.perspectivev3 20 | xcuserdata/ 21 | 22 | ## Other 23 | *.moved-aside 24 | *.xccheckout 25 | *.xcscmblueprint 26 | 27 | ## Obj-C/Swift specific 28 | *.hmap 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | .build/ 43 | 44 | # CocoaPods 45 | # 46 | # We recommend against adding the Pods directory to your .gitignore. However 47 | # you should judge for yourself, the pros and cons are mentioned at: 48 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 49 | # 50 | # Pods/ 51 | 52 | # Carthage 53 | # 54 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 55 | # Carthage/Checkouts 56 | 57 | Carthage/Build 58 | 59 | # fastlane 60 | # 61 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 62 | # screenshots whenever they are needed. 63 | # For more information about the recommended setup visit: 64 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 65 | 66 | fastlane/report.xml 67 | fastlane/Preview.html 68 | fastlane/screenshots 69 | fastlane/test_output 70 | -------------------------------------------------------------------------------- /HTMLWithImagesToPDF.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | /// 4 | /// Reads the given HTML file and replaces `{{ABSOLUTE_PATH}}` and `{{BASE64_STRING}}` with proper values. 5 | /// 6 | /// - parameters: 7 | /// - htmlFile: The HTML file. 8 | /// 9 | /// - returns: The transformed HTML code. 10 | func getHTML(from htmlFile: URL) -> String { 11 | 12 | guard let htmlContent = try? String(contentsOf: htmlFile) 13 | else { fatalError("Error getting HTML file content.") } 14 | 15 | guard let imgURL = Bundle.main.url(forResource: "apple", withExtension: "png") 16 | else { fatalError("Error locating image file.") } 17 | 18 | guard let base64String = try? Data(contentsOf: imgURL).base64EncodedString() 19 | else { fatalError("Error creating Base64-encoded string.") } 20 | 21 | return htmlContent 22 | .replacingOccurrences(of: "{{ABSOLUTE_PATH}}", with: imgURL.description) 23 | .replacingOccurrences(of: "{{BASE64_STRING}}", with: base64String) 24 | } 25 | 26 | /// 27 | /// Generates a PDF from the given HTML code and saves it to the given destination file. 28 | /// 29 | /// - parameters: 30 | /// - html: The HTML code. 31 | /// - destination: The URL indicating where to write the generated PDF file. 32 | /// 33 | func createPDF(with html: String, to destination: URL) { 34 | 35 | // create print formatter 36 | let printFormatter = UIMarkupTextPrintFormatter(markupText: html) 37 | 38 | // assign the print formatter to the print page renderer 39 | let renderer = UIPrintPageRenderer() 40 | renderer.addPrintFormatter(printFormatter, startingAtPageAt: 0) 41 | 42 | // assign paperRect and printableRect values 43 | let page = CGRect(x: 0, y: 0, width: 595.2, height: 841.8) // A4, 72 dpi 44 | renderer.setValue(page, forKey: "paperRect") 45 | renderer.setValue(page, forKey: "printableRect") 46 | 47 | // create pdf context and draw each page 48 | let pdfData = NSMutableData() 49 | UIGraphicsBeginPDFContextToData(pdfData, .zero, nil) 50 | 51 | for i in 0.. 2 | 3 | 4 | 5 | HTML With Images to PDF 6 | 7 | 8 |

Image loaded from the web:

9 | img-web 10 |

Image loaded from an absolute path:

11 | img-abs 12 |

Image loaded from a base64-encoded string:

13 | img-base64 14 | 15 | 16 | -------------------------------------------------------------------------------- /HTMLWithImagesToPDF.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /HTMLWithImagesToPDF.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | CE018BA41F3FAFA0007EB8D2 /* index-img.html in Resources */ = {isa = PBXBuildFile; fileRef = CE018BA31F3FAFA0007EB8D2 /* index-img.html */; }; 11 | CEBD8AF61F3E5DD60068CD11 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEBD8AF51F3E5DD60068CD11 /* AppDelegate.swift */; }; 12 | CEBD8AF81F3E5DD60068CD11 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEBD8AF71F3E5DD60068CD11 /* ViewController.swift */; }; 13 | CEBD8AFD1F3E5DD60068CD11 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CEBD8AFC1F3E5DD60068CD11 /* Assets.xcassets */; }; 14 | CEBD8B0A1F3E616D0068CD11 /* index.html in Resources */ = {isa = PBXBuildFile; fileRef = CEBD8B091F3E616D0068CD11 /* index.html */; }; 15 | CEBD8B0C1F3E65130068CD11 /* apple.png in Resources */ = {isa = PBXBuildFile; fileRef = CEBD8B0B1F3E65130068CD11 /* apple.png */; }; 16 | CEBD8B101F3F76F70068CD11 /* UIAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEBD8B0F1F3F76F70068CD11 /* UIAlertController.swift */; }; 17 | CEF30F3C1F72CBFD0040E6FF /* HTML.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEF30F3B1F72CBFD0040E6FF /* HTML.swift */; }; 18 | CEF30F3E1F72CDF90040E6FF /* PDF.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEF30F3D1F72CDF90040E6FF /* PDF.swift */; }; 19 | CEF30F401F72D6070040E6FF /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CEF30F371F72CB300040E6FF /* Main.storyboard */; }; 20 | CEF30F411F72D60A0040E6FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CEF30F381F72CB310040E6FF /* LaunchScreen.storyboard */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXFileReference section */ 24 | CE018BA31F3FAFA0007EB8D2 /* index-img.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "index-img.html"; sourceTree = ""; }; 25 | CEBD8AF21F3E5DD60068CD11 /* HTMLWithImagesToPDF.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = HTMLWithImagesToPDF.app; sourceTree = BUILT_PRODUCTS_DIR; }; 26 | CEBD8AF51F3E5DD60068CD11 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 27 | CEBD8AF71F3E5DD60068CD11 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 28 | CEBD8AFC1F3E5DD60068CD11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 29 | CEBD8B011F3E5DD60068CD11 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 30 | CEBD8B091F3E616D0068CD11 /* index.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = index.html; sourceTree = ""; }; 31 | CEBD8B0B1F3E65130068CD11 /* apple.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = apple.png; sourceTree = ""; }; 32 | CEBD8B0F1F3F76F70068CD11 /* UIAlertController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIAlertController.swift; sourceTree = ""; }; 33 | CEF30F371F72CB300040E6FF /* Main.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 34 | CEF30F381F72CB310040E6FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; 35 | CEF30F3B1F72CBFD0040E6FF /* HTML.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTML.swift; sourceTree = ""; }; 36 | CEF30F3D1F72CDF90040E6FF /* PDF.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PDF.swift; sourceTree = ""; }; 37 | /* End PBXFileReference section */ 38 | 39 | /* Begin PBXFrameworksBuildPhase section */ 40 | CEBD8AEF1F3E5DD60068CD11 /* Frameworks */ = { 41 | isa = PBXFrameworksBuildPhase; 42 | buildActionMask = 2147483647; 43 | files = ( 44 | ); 45 | runOnlyForDeploymentPostprocessing = 0; 46 | }; 47 | /* End PBXFrameworksBuildPhase section */ 48 | 49 | /* Begin PBXGroup section */ 50 | CEBD8AE91F3E5DD50068CD11 = { 51 | isa = PBXGroup; 52 | children = ( 53 | CEBD8AF41F3E5DD60068CD11 /* HTMLWithImagesToPDF */, 54 | CEBD8AF31F3E5DD60068CD11 /* Products */, 55 | ); 56 | sourceTree = ""; 57 | }; 58 | CEBD8AF31F3E5DD60068CD11 /* Products */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | CEBD8AF21F3E5DD60068CD11 /* HTMLWithImagesToPDF.app */, 62 | ); 63 | name = Products; 64 | sourceTree = ""; 65 | }; 66 | CEBD8AF41F3E5DD60068CD11 /* HTMLWithImagesToPDF */ = { 67 | isa = PBXGroup; 68 | children = ( 69 | CEF30F3A1F72CBBB0040E6FF /* Source */, 70 | CEF30F361F72CADF0040E6FF /* Storyboards */, 71 | CEF30F391F72CB6F0040E6FF /* Supporting Files */, 72 | ); 73 | path = HTMLWithImagesToPDF; 74 | sourceTree = ""; 75 | }; 76 | CEF30F361F72CADF0040E6FF /* Storyboards */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | CEF30F371F72CB300040E6FF /* Main.storyboard */, 80 | CEF30F381F72CB310040E6FF /* LaunchScreen.storyboard */, 81 | ); 82 | path = Storyboards; 83 | sourceTree = ""; 84 | }; 85 | CEF30F391F72CB6F0040E6FF /* Supporting Files */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | CEBD8AFC1F3E5DD60068CD11 /* Assets.xcassets */, 89 | CEBD8B011F3E5DD60068CD11 /* Info.plist */, 90 | CEBD8B091F3E616D0068CD11 /* index.html */, 91 | CE018BA31F3FAFA0007EB8D2 /* index-img.html */, 92 | CEBD8B0B1F3E65130068CD11 /* apple.png */, 93 | ); 94 | path = "Supporting Files"; 95 | sourceTree = ""; 96 | }; 97 | CEF30F3A1F72CBBB0040E6FF /* Source */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | CEBD8AF51F3E5DD60068CD11 /* AppDelegate.swift */, 101 | CEBD8AF71F3E5DD60068CD11 /* ViewController.swift */, 102 | CEF30F3B1F72CBFD0040E6FF /* HTML.swift */, 103 | CEF30F3D1F72CDF90040E6FF /* PDF.swift */, 104 | CEF30F3F1F72CE400040E6FF /* Extensions */, 105 | ); 106 | path = Source; 107 | sourceTree = ""; 108 | }; 109 | CEF30F3F1F72CE400040E6FF /* Extensions */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | CEBD8B0F1F3F76F70068CD11 /* UIAlertController.swift */, 113 | ); 114 | path = Extensions; 115 | sourceTree = ""; 116 | }; 117 | /* End PBXGroup section */ 118 | 119 | /* Begin PBXNativeTarget section */ 120 | CEBD8AF11F3E5DD60068CD11 /* HTMLWithImagesToPDF */ = { 121 | isa = PBXNativeTarget; 122 | buildConfigurationList = CEBD8B041F3E5DD60068CD11 /* Build configuration list for PBXNativeTarget "HTMLWithImagesToPDF" */; 123 | buildPhases = ( 124 | CEBD8AEE1F3E5DD60068CD11 /* Sources */, 125 | CEBD8AEF1F3E5DD60068CD11 /* Frameworks */, 126 | CEBD8AF01F3E5DD60068CD11 /* Resources */, 127 | ); 128 | buildRules = ( 129 | ); 130 | dependencies = ( 131 | ); 132 | name = HTMLWithImagesToPDF; 133 | productName = HTMLWithImagesToPDF; 134 | productReference = CEBD8AF21F3E5DD60068CD11 /* HTMLWithImagesToPDF.app */; 135 | productType = "com.apple.product-type.application"; 136 | }; 137 | /* End PBXNativeTarget section */ 138 | 139 | /* Begin PBXProject section */ 140 | CEBD8AEA1F3E5DD50068CD11 /* Project object */ = { 141 | isa = PBXProject; 142 | attributes = { 143 | LastSwiftUpdateCheck = 0830; 144 | LastUpgradeCheck = 0900; 145 | ORGANIZATIONNAME = nyg; 146 | TargetAttributes = { 147 | CEBD8AF11F3E5DD60068CD11 = { 148 | CreatedOnToolsVersion = 8.3.3; 149 | DevelopmentTeam = 7BPPXA6BRU; 150 | LastSwiftMigration = 0900; 151 | ProvisioningStyle = Automatic; 152 | }; 153 | }; 154 | }; 155 | buildConfigurationList = CEBD8AED1F3E5DD50068CD11 /* Build configuration list for PBXProject "HTMLWithImagesToPDF" */; 156 | compatibilityVersion = "Xcode 3.2"; 157 | developmentRegion = English; 158 | hasScannedForEncodings = 0; 159 | knownRegions = ( 160 | ); 161 | mainGroup = CEBD8AE91F3E5DD50068CD11; 162 | productRefGroup = CEBD8AF31F3E5DD60068CD11 /* Products */; 163 | projectDirPath = ""; 164 | projectRoot = ""; 165 | targets = ( 166 | CEBD8AF11F3E5DD60068CD11 /* HTMLWithImagesToPDF */, 167 | ); 168 | }; 169 | /* End PBXProject section */ 170 | 171 | /* Begin PBXResourcesBuildPhase section */ 172 | CEBD8AF01F3E5DD60068CD11 /* Resources */ = { 173 | isa = PBXResourcesBuildPhase; 174 | buildActionMask = 2147483647; 175 | files = ( 176 | CEBD8B0A1F3E616D0068CD11 /* index.html in Resources */, 177 | CEF30F401F72D6070040E6FF /* Main.storyboard in Resources */, 178 | CEBD8AFD1F3E5DD60068CD11 /* Assets.xcassets in Resources */, 179 | CEF30F411F72D60A0040E6FF /* LaunchScreen.storyboard in Resources */, 180 | CEBD8B0C1F3E65130068CD11 /* apple.png in Resources */, 181 | CE018BA41F3FAFA0007EB8D2 /* index-img.html in Resources */, 182 | ); 183 | runOnlyForDeploymentPostprocessing = 0; 184 | }; 185 | /* End PBXResourcesBuildPhase section */ 186 | 187 | /* Begin PBXSourcesBuildPhase section */ 188 | CEBD8AEE1F3E5DD60068CD11 /* Sources */ = { 189 | isa = PBXSourcesBuildPhase; 190 | buildActionMask = 2147483647; 191 | files = ( 192 | CEBD8B101F3F76F70068CD11 /* UIAlertController.swift in Sources */, 193 | CEF30F3E1F72CDF90040E6FF /* PDF.swift in Sources */, 194 | CEBD8AF81F3E5DD60068CD11 /* ViewController.swift in Sources */, 195 | CEF30F3C1F72CBFD0040E6FF /* HTML.swift in Sources */, 196 | CEBD8AF61F3E5DD60068CD11 /* AppDelegate.swift in Sources */, 197 | ); 198 | runOnlyForDeploymentPostprocessing = 0; 199 | }; 200 | /* End PBXSourcesBuildPhase section */ 201 | 202 | /* Begin XCBuildConfiguration section */ 203 | CEBD8B021F3E5DD60068CD11 /* Debug */ = { 204 | isa = XCBuildConfiguration; 205 | buildSettings = { 206 | ALWAYS_SEARCH_USER_PATHS = NO; 207 | CLANG_ANALYZER_NONNULL = YES; 208 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 209 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 210 | CLANG_CXX_LIBRARY = "libc++"; 211 | CLANG_ENABLE_MODULES = YES; 212 | CLANG_ENABLE_OBJC_ARC = YES; 213 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 214 | CLANG_WARN_BOOL_CONVERSION = YES; 215 | CLANG_WARN_COMMA = YES; 216 | CLANG_WARN_CONSTANT_CONVERSION = YES; 217 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 218 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 219 | CLANG_WARN_EMPTY_BODY = YES; 220 | CLANG_WARN_ENUM_CONVERSION = YES; 221 | CLANG_WARN_INFINITE_RECURSION = YES; 222 | CLANG_WARN_INT_CONVERSION = YES; 223 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 224 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 225 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 226 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 227 | CLANG_WARN_STRICT_PROTOTYPES = YES; 228 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 229 | CLANG_WARN_UNREACHABLE_CODE = YES; 230 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 231 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 232 | COPY_PHASE_STRIP = NO; 233 | DEBUG_INFORMATION_FORMAT = dwarf; 234 | ENABLE_STRICT_OBJC_MSGSEND = YES; 235 | ENABLE_TESTABILITY = YES; 236 | GCC_C_LANGUAGE_STANDARD = gnu99; 237 | GCC_DYNAMIC_NO_PIC = NO; 238 | GCC_NO_COMMON_BLOCKS = YES; 239 | GCC_OPTIMIZATION_LEVEL = 0; 240 | GCC_PREPROCESSOR_DEFINITIONS = ( 241 | "DEBUG=1", 242 | "$(inherited)", 243 | ); 244 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 245 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 246 | GCC_WARN_UNDECLARED_SELECTOR = YES; 247 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 248 | GCC_WARN_UNUSED_FUNCTION = YES; 249 | GCC_WARN_UNUSED_VARIABLE = YES; 250 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 251 | MTL_ENABLE_DEBUG_INFO = YES; 252 | ONLY_ACTIVE_ARCH = YES; 253 | SDKROOT = iphoneos; 254 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 255 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 256 | TARGETED_DEVICE_FAMILY = "1,2"; 257 | }; 258 | name = Debug; 259 | }; 260 | CEBD8B031F3E5DD60068CD11 /* Release */ = { 261 | isa = XCBuildConfiguration; 262 | buildSettings = { 263 | ALWAYS_SEARCH_USER_PATHS = NO; 264 | CLANG_ANALYZER_NONNULL = YES; 265 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 266 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 267 | CLANG_CXX_LIBRARY = "libc++"; 268 | CLANG_ENABLE_MODULES = YES; 269 | CLANG_ENABLE_OBJC_ARC = YES; 270 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 271 | CLANG_WARN_BOOL_CONVERSION = YES; 272 | CLANG_WARN_COMMA = YES; 273 | CLANG_WARN_CONSTANT_CONVERSION = YES; 274 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 275 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 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_NON_LITERAL_NULL_CONVERSION = YES; 281 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 282 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 283 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 284 | CLANG_WARN_STRICT_PROTOTYPES = YES; 285 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 286 | CLANG_WARN_UNREACHABLE_CODE = YES; 287 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 288 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 289 | COPY_PHASE_STRIP = NO; 290 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 291 | ENABLE_NS_ASSERTIONS = NO; 292 | ENABLE_STRICT_OBJC_MSGSEND = YES; 293 | GCC_C_LANGUAGE_STANDARD = gnu99; 294 | GCC_NO_COMMON_BLOCKS = YES; 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 = 11.0; 302 | MTL_ENABLE_DEBUG_INFO = NO; 303 | SDKROOT = iphoneos; 304 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 305 | TARGETED_DEVICE_FAMILY = "1,2"; 306 | VALIDATE_PRODUCT = YES; 307 | }; 308 | name = Release; 309 | }; 310 | CEBD8B051F3E5DD60068CD11 /* Debug */ = { 311 | isa = XCBuildConfiguration; 312 | buildSettings = { 313 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 314 | DEVELOPMENT_TEAM = 7BPPXA6BRU; 315 | INFOPLIST_FILE = "HTMLWithImagesToPDF/Supporting Files/Info.plist"; 316 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 317 | PRODUCT_BUNDLE_IDENTIFIER = ch.nyg.HTMLWithImagesToPDF; 318 | PRODUCT_NAME = "$(TARGET_NAME)"; 319 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 320 | SWIFT_VERSION = 4.0; 321 | }; 322 | name = Debug; 323 | }; 324 | CEBD8B061F3E5DD60068CD11 /* Release */ = { 325 | isa = XCBuildConfiguration; 326 | buildSettings = { 327 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 328 | DEVELOPMENT_TEAM = 7BPPXA6BRU; 329 | INFOPLIST_FILE = "HTMLWithImagesToPDF/Supporting Files/Info.plist"; 330 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 331 | PRODUCT_BUNDLE_IDENTIFIER = ch.nyg.HTMLWithImagesToPDF; 332 | PRODUCT_NAME = "$(TARGET_NAME)"; 333 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 334 | SWIFT_VERSION = 4.0; 335 | }; 336 | name = Release; 337 | }; 338 | /* End XCBuildConfiguration section */ 339 | 340 | /* Begin XCConfigurationList section */ 341 | CEBD8AED1F3E5DD50068CD11 /* Build configuration list for PBXProject "HTMLWithImagesToPDF" */ = { 342 | isa = XCConfigurationList; 343 | buildConfigurations = ( 344 | CEBD8B021F3E5DD60068CD11 /* Debug */, 345 | CEBD8B031F3E5DD60068CD11 /* Release */, 346 | ); 347 | defaultConfigurationIsVisible = 0; 348 | defaultConfigurationName = Release; 349 | }; 350 | CEBD8B041F3E5DD60068CD11 /* Build configuration list for PBXNativeTarget "HTMLWithImagesToPDF" */ = { 351 | isa = XCConfigurationList; 352 | buildConfigurations = ( 353 | CEBD8B051F3E5DD60068CD11 /* Debug */, 354 | CEBD8B061F3E5DD60068CD11 /* Release */, 355 | ); 356 | defaultConfigurationIsVisible = 0; 357 | defaultConfigurationName = Release; 358 | }; 359 | /* End XCConfigurationList section */ 360 | }; 361 | rootObject = CEBD8AEA1F3E5DD50068CD11 /* Project object */; 362 | } 363 | -------------------------------------------------------------------------------- /HTMLWithImagesToPDF.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /HTMLWithImagesToPDF/Source/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // HTMLWithImagesToPDF 4 | // 5 | // Created by user on 11.08.17. 6 | // Copyright © 2017 nyg. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | var window: UIWindow? 14 | } 15 | -------------------------------------------------------------------------------- /HTMLWithImagesToPDF/Source/Extensions/UIAlertController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIAlertController.swift 3 | // HTMLWithImagesToPDF 4 | // 5 | // Created by user on 12.08.17. 6 | // Copyright © 2017 nyg. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIAlertController { 12 | 13 | @discardableResult 14 | func addAction(title: String?, style: UIAlertActionStyle = .default, handler: ((UIAlertAction) -> Void)? = nil) -> Self { 15 | addAction(UIAlertAction(title: title, style: style, handler: handler)) 16 | return self 17 | } 18 | 19 | func present(by viewController: UIViewController) { 20 | viewController.present(self, animated: true) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /HTMLWithImagesToPDF/Source/HTML.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HTML.swift 3 | // HTMLWithImagesToPDF 4 | // 5 | // Created by user on 20.09.17. 6 | // Copyright © 2017 nyg. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class HTML { 12 | 13 | /** 14 | Reads the given HTML file and replaces `{{ABSOLUTE_PATH}}` and `{{BASE64_STRING}}` with proper values. 15 | 16 | - parameters: 17 | - fileName: The name of the HTML file. 18 | 19 | - returns: The transformed HTML code. 20 | */ 21 | class func get(from fileName: String) -> String { 22 | 23 | guard let htmlFile = Bundle.main.url(forResource: fileName, withExtension: nil) 24 | else { fatalError("Error locating HTML file.") } 25 | 26 | guard let htmlContent = try? String(contentsOf: htmlFile) 27 | else { fatalError("Error getting HTML file content.") } 28 | 29 | guard let imageURL = Bundle.main.url(forResource: "apple", withExtension: "png") 30 | else { fatalError("Error locating image file.") } 31 | 32 | // create base64-encoded string of the "apple" image (located in Assets.xcassets) 33 | guard let base64String = UIImagePNGRepresentation(#imageLiteral(resourceName: "apple"))?.base64EncodedString() 34 | else { fatalError("Error creating PNG representation.") } 35 | 36 | return htmlContent 37 | .replacingOccurrences(of: "{{ABSOLUTE_PATH}}", with: imageURL.description) 38 | .replacingOccurrences(of: "{{BASE64_STRING}}", with: base64String) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /HTMLWithImagesToPDF/Source/PDF.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PDF.swift 3 | // HTMLWithImagesToPDF 4 | // 5 | // Created by user on 20.09.17. 6 | // Copyright © 2017 nyg. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class PDF { 12 | 13 | /** 14 | Generates a PDF using the given print formatter and saves it to the user's document directory. 15 | 16 | - parameters: 17 | - printFormatter: The print formatter used to generate the PDF. 18 | 19 | - returns: The generated PDF. 20 | */ 21 | class func generate(using printFormatter: UIPrintFormatter) -> Data { 22 | 23 | // assign the print formatter to the print page renderer 24 | let renderer = UIPrintPageRenderer() 25 | renderer.addPrintFormatter(printFormatter, startingAtPageAt: 0) 26 | 27 | // assign paperRect and printableRect values 28 | let page = CGRect(x: 0, y: 0, width: 595.2, height: 841.8) // A4, 72 dpi 29 | renderer.setValue(page, forKey: "paperRect") 30 | renderer.setValue(page, forKey: "printableRect") 31 | 32 | // create pdf context and draw each page 33 | let pdfData = NSMutableData() 34 | UIGraphicsBeginPDFContextToData(pdfData, .zero, nil) 35 | 36 | for i in 0.. UIPrintFormatter { 108 | return wkWebView.viewPrintFormatter() 109 | } 110 | 111 | private func uiWebViewPrintFormatter() -> UIPrintFormatter { 112 | return uiWebView.viewPrintFormatter() 113 | } 114 | 115 | private func markupTextPrintFormatter() -> UIPrintFormatter { 116 | return UIMarkupTextPrintFormatter(markupText: HTML.get(from: "index.html")) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /HTMLWithImagesToPDF/Storyboards/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 | -------------------------------------------------------------------------------- /HTMLWithImagesToPDF/Storyboards/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Menlo-Regular 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 76 | 83 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /HTMLWithImagesToPDF/Supporting Files/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /HTMLWithImagesToPDF/Supporting Files/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /HTMLWithImagesToPDF/Supporting Files/Assets.xcassets/apple.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "apple.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /HTMLWithImagesToPDF/Supporting Files/Assets.xcassets/apple.imageset/apple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nyg/HTMLWithImagesToPDF/b2cf66edffef5729fc768991dbc7fa78b0eb8a91/HTMLWithImagesToPDF/Supporting Files/Assets.xcassets/apple.imageset/apple.png -------------------------------------------------------------------------------- /HTMLWithImagesToPDF/Supporting Files/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | UIInterfaceOrientationPortraitUpsideDown 37 | 38 | UISupportedInterfaceOrientations~ipad 39 | 40 | UIInterfaceOrientationPortrait 41 | UIInterfaceOrientationPortraitUpsideDown 42 | UIInterfaceOrientationLandscapeLeft 43 | UIInterfaceOrientationLandscapeRight 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /HTMLWithImagesToPDF/Supporting Files/apple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nyg/HTMLWithImagesToPDF/b2cf66edffef5729fc768991dbc7fa78b0eb8a91/HTMLWithImagesToPDF/Supporting Files/apple.png -------------------------------------------------------------------------------- /HTMLWithImagesToPDF/Supporting Files/index-img.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | HTML With Images to PDF 6 | 7 | 8 | 9 | web-img 10 | xcode-img-rel 11 | xcode-img-abs 12 | base64-img 13 | 14 | 15 | -------------------------------------------------------------------------------- /HTMLWithImagesToPDF/Supporting Files/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | HTML With Images to PDF 6 | 11 | 12 | 13 |

Image loaded from the Web:

14 | web-img 15 |

Image loaded from the Xcode project with relative path:

16 | xcode-img-rel 17 |

Image loaded from the Xcode project with absolute path:

18 | xcode-img-abs 19 |

Image loaded from a Base64-encoded string:

20 | base64-img 21 | 22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HTMLWithImagesToPDF 2 | 3 | ## Description 4 | 5 | This app showcases the following: 6 | 7 | 1. Loading an HTML file into a web view (actually both a `WKWebView` and `UIWebView`), 8 | 2. Generating a PDF using either 9 | 1. the web views' `viewPrintFormatter()` or 10 | 2. a `UIMarkupTextPrintFormatter`, 11 | 3. Printing a PDF using either 12 | 1. one of the web views' `viewPrintFormatter()` or 13 | 2. a `UIMarkupTextPrintFormatter` 14 | 15 | 16 | ## Bug 17 | 18 | Generating a PDF using a `UIMarkupTextPrintFormatter` will not render the images of the HTML file. Unless the HTML code has been loaded into a web view beforehand. 19 | 20 | 1. Run the application. 21 | 2. Tap `Create PDF` and then `UIMarkupTextPrintFormatter`: a PDF is generated from the `index.html` file and loaded into both a `WKWebView` and a `UIWebView`. Images are not rendered. 22 | 3. Tap `Load HTML` and then `index-img.html`: the HTML code is loaded into both web views and the images are displayed. The HTML code is not the same as the one we used to generate our PDF (**but the `img` tags are the same**). 23 | 7. Re-do step 1: tap `Create PDF` and then `UIMarkupTextPrintFormatter`: another PDF is generated and loaded into the web views. And this time **with** images. 24 | 25 | This behavior tells me that `UIMarkupTextPrintFormatter` does support `img` tags. Also confirmed by the fact that `Print PDF` > `UIMarkupTextPrintFormatter` shows a PDF with images (actually only one image is displayed). 26 | 27 | ## Note 28 | 29 | The `Create PDF` > `WebView.viewPrintFormatter()` action creates a PDF from what each web view is **currently** displaying, that can either be nothing, one of the HTML loaded beforehand or a PDF loaded from a previous execution. 30 | 31 | The `Create PDF` > `UIMarkupTextPrintFormatter` action always creates the PDF from the `index.html` file. --------------------------------------------------------------------------------