├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── LICENSE.md
├── Package.swift
├── README.md
├── RichEditorView.podspec
├── RichEditorView.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── xcshareddata
│ └── xcschemes
│ └── RichEditorView.xcscheme
├── RichEditorView
├── Info.plist
├── RichEditorView-Bridging-Header.h
├── RichEditorView.h
└── Sources
│ ├── Resources
│ ├── editor
│ │ ├── assert.js
│ │ ├── normalize.css
│ │ ├── rich_editor.html
│ │ ├── rich_editor.js
│ │ ├── rich_editor_tests.html
│ │ ├── rich_editor_tests.js
│ │ └── style.css
│ └── icons
│ │ ├── bg_color@2x.png
│ │ ├── bold@2x.png
│ │ ├── bold@3x.png
│ │ ├── clear@2x.png
│ │ ├── h1@2x.png
│ │ ├── h2@2x.png
│ │ ├── h3@2x.png
│ │ ├── h4@2x.png
│ │ ├── h5@2x.png
│ │ ├── h6@2x.png
│ │ ├── indent@2x.png
│ │ ├── indent@3x.png
│ │ ├── insert_image@2x.png
│ │ ├── insert_link@2x.png
│ │ ├── italic@2x.png
│ │ ├── italic@3x.png
│ │ ├── justify_center@2x.png
│ │ ├── justify_left@2x.png
│ │ ├── justify_right@2x.png
│ │ ├── ordered_list@2x.png
│ │ ├── ordered_list@3x.png
│ │ ├── outdent@2x.png
│ │ ├── outdent@3x.png
│ │ ├── redo@2x.png
│ │ ├── redo@3x.png
│ │ ├── strikethrough@2x.png
│ │ ├── subscript@2x.png
│ │ ├── superscript@2x.png
│ │ ├── text_color@2x.png
│ │ ├── underline@2x.png
│ │ ├── underline@3x.png
│ │ ├── undo@2x.png
│ │ ├── undo@3x.png
│ │ ├── unordered_list@2x.png
│ │ └── unordered_list@3x.png
│ ├── RichEditorOptionItem.swift
│ ├── RichEditorToolbar.swift
│ ├── RichEditorView.swift
│ ├── RichEditorWebView.swift
│ ├── String+Extensions.swift
│ └── UIColor+Extensions.swift
├── RichEditorViewSample
├── Podfile
├── Podfile.lock
├── Pods
│ ├── Manifest.lock
│ ├── Pods.xcodeproj
│ │ └── project.pbxproj
│ └── Target Support Files
│ │ └── Pods-RichEditorViewSample
│ │ ├── Pods-RichEditorViewSample-Info.plist
│ │ ├── Pods-RichEditorViewSample-acknowledgements.markdown
│ │ ├── Pods-RichEditorViewSample-acknowledgements.plist
│ │ ├── Pods-RichEditorViewSample-dummy.m
│ │ ├── Pods-RichEditorViewSample-frameworks.sh
│ │ ├── Pods-RichEditorViewSample-umbrella.h
│ │ ├── Pods-RichEditorViewSample.debug.xcconfig
│ │ ├── Pods-RichEditorViewSample.modulemap
│ │ └── Pods-RichEditorViewSample.release.xcconfig
├── RichEditorViewSample.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── RichEditorViewSample.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ ├── IDEWorkspaceChecks.plist
│ │ └── swiftpm
│ │ └── Package.resolved
├── RichEditorViewSample
│ ├── AppDelegate.swift
│ ├── Base.lproj
│ │ ├── LaunchScreen.xib
│ │ └── Main.storyboard
│ ├── Images.xcassets
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── Info.plist
│ ├── KeyboardManager.swift
│ ├── RichEditorViewSample-Bridging-Header.h
│ └── ViewController.swift
└── RichEditorViewSampleTests
│ ├── Info.plist
│ └── RichEditorViewSampleTests.swift
├── RichEditorViewTests
├── Info.plist
└── RichEditorViewTests.swift
└── art
├── Demo.gif
└── Toolbar.gif
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | .build
4 | build/
5 | *.pbxuser
6 | !default.pbxuser
7 | *.mode1v3
8 | !default.mode1v3
9 | *.mode2v3
10 | !default.mode2v3
11 | *.perspectivev3
12 | !default.perspectivev3
13 | xcuserdata
14 | *.xccheckout
15 | *.moved-aside
16 | DerivedData
17 | *.hmap
18 | *.ipa
19 | *.xcuserstate
20 |
21 | .DS_Store
22 | .AppleDouble
23 | .LSOverride
24 |
25 | # Icon must end with two \r
26 | Icon
27 |
28 |
29 | # Thumbnails
30 | ._*
31 |
32 | # Files that might appear in the root of a volume
33 | .DocumentRevisions-V100
34 | .fseventsd
35 | .Spotlight-V100
36 | .TemporaryItems
37 | .Trashes
38 | .VolumeIcon.icns
39 |
40 | # Directories potentially created on remote AFP share
41 | .AppleDB
42 | .AppleDesktop
43 | Network Trash Folder
44 | Temporary Items
45 | .apdisk
46 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
'); 243 | }; 244 | 245 | RE.insertHTML = function(html) { 246 | RE.restorerange(); 247 | document.execCommand('insertHTML', false, html); 248 | }; 249 | 250 | RE.insertLink = function(url, title) { 251 | RE.restorerange(); 252 | var sel = document.getSelection(); 253 | if (sel.toString().length !== 0) { 254 | if (sel.rangeCount) { 255 | 256 | var el = document.createElement("a"); 257 | el.setAttribute("href", url); 258 | el.setAttribute("title", title); 259 | 260 | var range = sel.getRangeAt(0).cloneRange(); 261 | range.surroundContents(el); 262 | sel.removeAllRanges(); 263 | sel.addRange(range); 264 | } 265 | } 266 | RE.callback("input"); 267 | }; 268 | 269 | RE.prepareInsert = function() { 270 | RE.backuprange(); 271 | }; 272 | 273 | RE.backuprange = function() { 274 | var selection = window.getSelection(); 275 | if (selection.rangeCount > 0) { 276 | var range = selection.getRangeAt(0); 277 | RE.currentSelection = { 278 | "startContainer": range.startContainer, 279 | "startOffset": range.startOffset, 280 | "endContainer": range.endContainer, 281 | "endOffset": range.endOffset 282 | }; 283 | } 284 | }; 285 | 286 | RE.addRangeToSelection = function(selection, range) { 287 | if (selection) { 288 | selection.removeAllRanges(); 289 | selection.addRange(range); 290 | } 291 | }; 292 | 293 | // Programatically select a DOM element 294 | RE.selectElementContents = function(el) { 295 | var range = document.createRange(); 296 | range.selectNodeContents(el); 297 | var sel = window.getSelection(); 298 | // this.createSelectionFromRange sel, range 299 | RE.addRangeToSelection(sel, range); 300 | }; 301 | 302 | RE.restorerange = function() { 303 | var selection = window.getSelection(); 304 | selection.removeAllRanges(); 305 | var range = document.createRange(); 306 | range.setStart(RE.currentSelection.startContainer, RE.currentSelection.startOffset); 307 | range.setEnd(RE.currentSelection.endContainer, RE.currentSelection.endOffset); 308 | selection.addRange(range); 309 | }; 310 | 311 | RE.focus = function() { 312 | var range = document.createRange(); 313 | range.selectNodeContents(RE.editor); 314 | range.collapse(false); 315 | var selection = window.getSelection(); 316 | selection.removeAllRanges(); 317 | selection.addRange(range); 318 | RE.editor.focus(); 319 | }; 320 | 321 | RE.focusAtPoint = function(x, y) { 322 | var range = document.caretRangeFromPoint(x, y) || document.createRange(); 323 | var selection = window.getSelection(); 324 | selection.removeAllRanges(); 325 | selection.addRange(range); 326 | RE.editor.focus(); 327 | }; 328 | 329 | RE.blurFocus = function() { 330 | RE.editor.blur(); 331 | }; 332 | 333 | /** 334 | Recursively search element ancestors to find a element nodeName e.g. A 335 | **/ 336 | var _findNodeByNameInContainer = function(element, nodeName, rootElementId) { 337 | if (element.nodeName == nodeName) { 338 | return element; 339 | } else { 340 | if (element.id === rootElementId) { 341 | return null; 342 | } 343 | _findNodeByNameInContainer(element.parentElement, nodeName, rootElementId); 344 | } 345 | }; 346 | 347 | var isAnchorNode = function(node) { 348 | return ("A" == node.nodeName); 349 | }; 350 | 351 | RE.getAnchorTagsInNode = function(node) { 352 | var links = []; 353 | 354 | while (node.nextSibling !== null && node.nextSibling !== undefined) { 355 | node = node.nextSibling; 356 | if (isAnchorNode(node)) { 357 | links.push(node.getAttribute('href')); 358 | } 359 | } 360 | return links; 361 | }; 362 | 363 | RE.countAnchorTagsInNode = function(node) { 364 | return RE.getAnchorTagsInNode(node).length; 365 | }; 366 | 367 | /** 368 | * If the current selection's parent is an anchor tag, get the href. 369 | * @returns {string} 370 | */ 371 | RE.getSelectedHref = function() { 372 | var href, sel; 373 | href = ''; 374 | sel = window.getSelection(); 375 | if (!RE.rangeOrCaretSelectionExists()) { 376 | return null; 377 | } 378 | 379 | var tags = RE.getAnchorTagsInNode(sel.anchorNode); 380 | //if more than one link is there, return null 381 | if (tags.length > 1) { 382 | return null; 383 | } else if (tags.length == 1) { 384 | href = tags[0]; 385 | } else { 386 | var node = _findNodeByNameInContainer(sel.anchorNode.parentElement, 'A', 'editor'); 387 | href = node.href; 388 | } 389 | 390 | return href ? href : null; 391 | }; 392 | 393 | // Returns the cursor position relative to its current position onscreen. 394 | // Can be negative if it is above what is visible 395 | RE.getRelativeCaretYPosition = function() { 396 | var y = 0; 397 | var sel = window.getSelection(); 398 | if (sel.rangeCount) { 399 | var range = sel.getRangeAt(0); 400 | var needsWorkAround = (range.startOffset == 0) 401 | /* Removing fixes bug when node name other than 'div' */ 402 | // && range.startContainer.nodeName.toLowerCase() == 'div'); 403 | if (needsWorkAround) { 404 | y = range.startContainer.offsetTop - window.pageYOffset; 405 | } else { 406 | if (range.getClientRects) { 407 | var rects = range.getClientRects(); 408 | if (rects.length > 0) { 409 | y = rects[0].top; 410 | } 411 | } 412 | } 413 | } 414 | 415 | return y; 416 | }; 417 | 418 | window.onload = function() { 419 | RE.callback("ready"); 420 | }; 421 | -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/editor/rich_editor_tests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |10 |11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/editor/rich_editor_tests.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var RichEditorTests = function() { 4 | var self = {}; 5 | var tests = []; 6 | var link = "http://foo.bar/"; 7 | var anchor = "Foo"; 8 | var htmlWithLink = "What are these so withered and wild in their attire? " + anchor + "
that look not like the inhabitants of the Earth and yet are on't?"; 9 | var htmlWith2Links = "Blah? " + anchor + " " + anchor + " Blah
"; 10 | 11 | var tearDown = function() { 12 | RE.setHtml(''); 13 | }; 14 | 15 | /** 16 | This is the main and only public "method" 17 | **/ 18 | self.runTests = function() { 19 | var content = ""; 20 | for (var testName in tests) { 21 | tests[testName](); 22 | var log = 'Passed : ' + testName; 23 | console.log(log); 24 | content += log + "
"; 25 | tearDown(); 26 | } 27 | RE.setHtml(content); 28 | }; 29 | 30 | tests['testGetSet'] = function() { 31 | var testContent = "Test"; 32 | RE.setHtml(testContent); 33 | Assert.equals(RE.getHtml(), testContent, 'testGetSet'); 34 | }; 35 | 36 | tests['testGetSelectedHrefReturnsLinkOnFullSelection'] = function() { 37 | let htmlWithLink = "Foo"; 38 | RE.setHtml(htmlWithLink); 39 | //select the anchor tag directly and fully 40 | RE.selectElementContents(document.querySelector('#link_id')); 41 | Assert.equals(RE.getSelectedHref(), link); 42 | }; 43 | 44 | tests['testGetSelectedHrefWithSelectionContainingOneLink'] = function() { 45 | RE.setHtml(htmlWithLink); 46 | //select the anchor tag directly and fully 47 | RE.selectElementContents(document.querySelector('#prose')); 48 | Assert.equals(RE.getSelectedHref(), link); 49 | }; 50 | 51 | tests['testCountAnchorTagsInSelection'] = function() { 52 | RE.setHtml(htmlWithLink); 53 | //select the anchor tag directly and fully 54 | RE.selectElementContents(document.querySelector('#prose')); 55 | let count = RE.countAnchorTagsInNode(getSelection().anchorNode); 56 | Assert.equals(count, 1); 57 | }; 58 | 59 | tests['testgetSelectedHrefWith2LinksReturnsNull'] = function() { 60 | RE.setHtml(htmlWith2Links); 61 | 62 | //select the anchor tag directly and fully 63 | RE.selectElementContents(document.querySelector('#two_links')); 64 | let count = RE.countAnchorTagsInNode(getSelection().anchorNode); 65 | Assert.equals(count, 2); 66 | // Assert.equals(RE.getSelectedHref(), null) 67 | }; 68 | 69 | return self; 70 | }(); 71 | 72 | RichEditorTests.runTests(); -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/editor/style.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (C) 2015 Wasabeef 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @charset "UTF-8"; 18 | 19 | :root { 20 | color-scheme: light dark; 21 | } 22 | 23 | body, html { 24 | height: 100%; 25 | } 26 | 27 | body { 28 | overflow: auto; 29 | margin: 0; 30 | font: -apple-system-body; 31 | } 32 | 33 | @media (prefers-color-scheme: dark) { 34 | body, #editor { 35 | } 36 | } 37 | 38 | #container { 39 | display: table; 40 | width: 100%; 41 | height: 100%; 42 | } 43 | 44 | #editor { 45 | -webkit-user-select: auto !important; 46 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 47 | overflow: auto; 48 | display: table-cell; 49 | height: 100%; 50 | } 51 | 52 | #editor:focus { 53 | outline: 0px solid transparent; 54 | } 55 | 56 | .placeholder[placeholder]:after { 57 | content: attr(placeholder); 58 | position: absolute; 59 | top: 0px; 60 | color: #ccc; 61 | } 62 | 63 | h1, p, ul, ol { 64 | margin: 0; 65 | } 66 | -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/icons/bg_color@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Pro/RichEditorView/0f90dfcfd2e6b8f63b8d6cdddeb70cc103dd2afd/RichEditorView/Sources/Resources/icons/bg_color@2x.png -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/icons/bold@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Pro/RichEditorView/0f90dfcfd2e6b8f63b8d6cdddeb70cc103dd2afd/RichEditorView/Sources/Resources/icons/bold@2x.png -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/icons/bold@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Pro/RichEditorView/0f90dfcfd2e6b8f63b8d6cdddeb70cc103dd2afd/RichEditorView/Sources/Resources/icons/bold@3x.png -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/icons/clear@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Pro/RichEditorView/0f90dfcfd2e6b8f63b8d6cdddeb70cc103dd2afd/RichEditorView/Sources/Resources/icons/clear@2x.png -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/icons/h1@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Pro/RichEditorView/0f90dfcfd2e6b8f63b8d6cdddeb70cc103dd2afd/RichEditorView/Sources/Resources/icons/h1@2x.png -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/icons/h2@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Pro/RichEditorView/0f90dfcfd2e6b8f63b8d6cdddeb70cc103dd2afd/RichEditorView/Sources/Resources/icons/h2@2x.png -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/icons/h3@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Pro/RichEditorView/0f90dfcfd2e6b8f63b8d6cdddeb70cc103dd2afd/RichEditorView/Sources/Resources/icons/h3@2x.png -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/icons/h4@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Pro/RichEditorView/0f90dfcfd2e6b8f63b8d6cdddeb70cc103dd2afd/RichEditorView/Sources/Resources/icons/h4@2x.png -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/icons/h5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Pro/RichEditorView/0f90dfcfd2e6b8f63b8d6cdddeb70cc103dd2afd/RichEditorView/Sources/Resources/icons/h5@2x.png -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/icons/h6@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Pro/RichEditorView/0f90dfcfd2e6b8f63b8d6cdddeb70cc103dd2afd/RichEditorView/Sources/Resources/icons/h6@2x.png -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/icons/indent@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Pro/RichEditorView/0f90dfcfd2e6b8f63b8d6cdddeb70cc103dd2afd/RichEditorView/Sources/Resources/icons/indent@2x.png -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/icons/indent@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Pro/RichEditorView/0f90dfcfd2e6b8f63b8d6cdddeb70cc103dd2afd/RichEditorView/Sources/Resources/icons/indent@3x.png -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/icons/insert_image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Pro/RichEditorView/0f90dfcfd2e6b8f63b8d6cdddeb70cc103dd2afd/RichEditorView/Sources/Resources/icons/insert_image@2x.png -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/icons/insert_link@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Pro/RichEditorView/0f90dfcfd2e6b8f63b8d6cdddeb70cc103dd2afd/RichEditorView/Sources/Resources/icons/insert_link@2x.png -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/icons/italic@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Pro/RichEditorView/0f90dfcfd2e6b8f63b8d6cdddeb70cc103dd2afd/RichEditorView/Sources/Resources/icons/italic@2x.png -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/icons/italic@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Pro/RichEditorView/0f90dfcfd2e6b8f63b8d6cdddeb70cc103dd2afd/RichEditorView/Sources/Resources/icons/italic@3x.png -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/icons/justify_center@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Pro/RichEditorView/0f90dfcfd2e6b8f63b8d6cdddeb70cc103dd2afd/RichEditorView/Sources/Resources/icons/justify_center@2x.png -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/icons/justify_left@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Pro/RichEditorView/0f90dfcfd2e6b8f63b8d6cdddeb70cc103dd2afd/RichEditorView/Sources/Resources/icons/justify_left@2x.png -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/icons/justify_right@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Pro/RichEditorView/0f90dfcfd2e6b8f63b8d6cdddeb70cc103dd2afd/RichEditorView/Sources/Resources/icons/justify_right@2x.png -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/icons/ordered_list@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Pro/RichEditorView/0f90dfcfd2e6b8f63b8d6cdddeb70cc103dd2afd/RichEditorView/Sources/Resources/icons/ordered_list@2x.png -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/icons/ordered_list@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Pro/RichEditorView/0f90dfcfd2e6b8f63b8d6cdddeb70cc103dd2afd/RichEditorView/Sources/Resources/icons/ordered_list@3x.png -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/icons/outdent@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Pro/RichEditorView/0f90dfcfd2e6b8f63b8d6cdddeb70cc103dd2afd/RichEditorView/Sources/Resources/icons/outdent@2x.png -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/icons/outdent@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Pro/RichEditorView/0f90dfcfd2e6b8f63b8d6cdddeb70cc103dd2afd/RichEditorView/Sources/Resources/icons/outdent@3x.png -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/icons/redo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Pro/RichEditorView/0f90dfcfd2e6b8f63b8d6cdddeb70cc103dd2afd/RichEditorView/Sources/Resources/icons/redo@2x.png -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/icons/redo@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Pro/RichEditorView/0f90dfcfd2e6b8f63b8d6cdddeb70cc103dd2afd/RichEditorView/Sources/Resources/icons/redo@3x.png -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/icons/strikethrough@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Pro/RichEditorView/0f90dfcfd2e6b8f63b8d6cdddeb70cc103dd2afd/RichEditorView/Sources/Resources/icons/strikethrough@2x.png -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/icons/subscript@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Pro/RichEditorView/0f90dfcfd2e6b8f63b8d6cdddeb70cc103dd2afd/RichEditorView/Sources/Resources/icons/subscript@2x.png -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/icons/superscript@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Pro/RichEditorView/0f90dfcfd2e6b8f63b8d6cdddeb70cc103dd2afd/RichEditorView/Sources/Resources/icons/superscript@2x.png -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/icons/text_color@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Pro/RichEditorView/0f90dfcfd2e6b8f63b8d6cdddeb70cc103dd2afd/RichEditorView/Sources/Resources/icons/text_color@2x.png -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/icons/underline@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Pro/RichEditorView/0f90dfcfd2e6b8f63b8d6cdddeb70cc103dd2afd/RichEditorView/Sources/Resources/icons/underline@2x.png -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/icons/underline@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Pro/RichEditorView/0f90dfcfd2e6b8f63b8d6cdddeb70cc103dd2afd/RichEditorView/Sources/Resources/icons/underline@3x.png -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/icons/undo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Pro/RichEditorView/0f90dfcfd2e6b8f63b8d6cdddeb70cc103dd2afd/RichEditorView/Sources/Resources/icons/undo@2x.png -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/icons/undo@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Pro/RichEditorView/0f90dfcfd2e6b8f63b8d6cdddeb70cc103dd2afd/RichEditorView/Sources/Resources/icons/undo@3x.png -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/icons/unordered_list@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Pro/RichEditorView/0f90dfcfd2e6b8f63b8d6cdddeb70cc103dd2afd/RichEditorView/Sources/Resources/icons/unordered_list@2x.png -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/icons/unordered_list@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Pro/RichEditorView/0f90dfcfd2e6b8f63b8d6cdddeb70cc103dd2afd/RichEditorView/Sources/Resources/icons/unordered_list@3x.png -------------------------------------------------------------------------------- /RichEditorView/Sources/RichEditorOptionItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RichEditorOptionItem.swift 3 | // 4 | // Created by Caesar Wirth on 4/2/15. 5 | // Copyright (c) 2015 Caesar Wirth. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | 10 | /// A RichEditorOption object is an object that can be displayed in a RichEditorToolbar. 11 | /// This protocol is proviced to allow for custom actions not provided in the RichEditorOptions enum. 12 | public protocol RichEditorOption { 13 | 14 | /// The image to be displayed in the RichEditorToolbar. 15 | var image: UIImage? { get } 16 | 17 | /// The title of the item. 18 | /// If `image` is nil, this will be used for display in the RichEditorToolbar. 19 | var title: String { get } 20 | 21 | /// The action to be evoked when the action is tapped 22 | /// - parameter editor: The RichEditorToolbar that the RichEditorOption was being displayed in when tapped. 23 | /// Contains a reference to the `editor` RichEditorView to perform actions on. 24 | func action(_ editor: RichEditorToolbar) 25 | } 26 | 27 | /// RichEditorOptionItem is a concrete implementation of RichEditorOption. 28 | /// It can be used as a configuration object for custom objects to be shown on a RichEditorToolbar. 29 | public struct RichEditorOptionItem: RichEditorOption { 30 | 31 | /// The image that should be shown when displayed in the RichEditorToolbar. 32 | public var image: UIImage? 33 | 34 | /// If an `itemImage` is not specified, this is used in display 35 | public var title: String 36 | 37 | /// The action to be performed when tapped 38 | public var handler: ((RichEditorToolbar) -> Void) 39 | 40 | public init(image: UIImage?, title: String, action: @escaping ((RichEditorToolbar) -> Void)) { 41 | self.image = image 42 | self.title = title 43 | self.handler = action 44 | } 45 | 46 | // MARK: RichEditorOption 47 | 48 | public func action(_ toolbar: RichEditorToolbar) { 49 | handler(toolbar) 50 | } 51 | } 52 | 53 | /// RichEditorOptions is an enum of standard editor actions 54 | public enum RichEditorDefaultOption: RichEditorOption { 55 | 56 | case clear 57 | case undo 58 | case redo 59 | case bold 60 | case italic 61 | case `subscript` 62 | case superscript 63 | case strike 64 | case underline 65 | case textColor 66 | case textBackgroundColor 67 | case header(Int) 68 | case indent 69 | case outdent 70 | case orderedList 71 | case unorderedList 72 | case alignLeft 73 | case alignCenter 74 | case alignRight 75 | case image 76 | case link 77 | 78 | public static let all: [RichEditorDefaultOption] = [ 79 | //.clear, 80 | .undo, .redo, .bold, .italic, 81 | .subscript, .superscript, .strike, .underline, 82 | .textColor, .textBackgroundColor, 83 | .header(1), .header(2), .header(3), .header(4), .header(5), .header(6), 84 | .indent, outdent, orderedList, unorderedList, 85 | .alignLeft, .alignCenter, .alignRight, .image, .link 86 | ] 87 | 88 | // MARK: RichEditorOption 89 | 90 | public var image: UIImage? { 91 | var name = "" 92 | switch self { 93 | case .clear: name = "clear" 94 | case .undo: name = "undo" 95 | case .redo: name = "redo" 96 | case .bold: name = "bold" 97 | case .italic: name = "italic" 98 | case .subscript: name = "subscript" 99 | case .superscript: name = "superscript" 100 | case .strike: name = "strikethrough" 101 | case .underline: name = "underline" 102 | case .textColor: name = "text_color" 103 | case .textBackgroundColor: name = "bg_color" 104 | case .header(let h): name = "h\(h)" 105 | case .indent: name = "indent" 106 | case .outdent: name = "outdent" 107 | case .orderedList: name = "ordered_list" 108 | case .unorderedList: name = "unordered_list" 109 | case .alignLeft: name = "justify_left" 110 | case .alignCenter: name = "justify_center" 111 | case .alignRight: name = "justify_right" 112 | case .image: name = "insert_image" 113 | case .link: name = "insert_link" 114 | } 115 | 116 | let bundle: Bundle 117 | #if SWIFT_PACKAGE 118 | bundle = Bundle.module 119 | #else 120 | bundle = Bundle(for: RichEditorToolbar.self) 121 | #endif 122 | return UIImage(named: name, in: bundle, compatibleWith: nil) 123 | } 124 | 125 | public var title: String { 126 | switch self { 127 | case .clear: return NSLocalizedString("Clear", comment: "") 128 | case .undo: return NSLocalizedString("Undo", comment: "") 129 | case .redo: return NSLocalizedString("Redo", comment: "") 130 | case .bold: return NSLocalizedString("Bold", comment: "") 131 | case .italic: return NSLocalizedString("Italic", comment: "") 132 | case .subscript: return NSLocalizedString("Sub", comment: "") 133 | case .superscript: return NSLocalizedString("Super", comment: "") 134 | case .strike: return NSLocalizedString("Strike", comment: "") 135 | case .underline: return NSLocalizedString("Underline", comment: "") 136 | case .textColor: return NSLocalizedString("Color", comment: "") 137 | case .textBackgroundColor: return NSLocalizedString("BG Color", comment: "") 138 | case .header(let h): return NSLocalizedString("H\(h)", comment: "") 139 | case .indent: return NSLocalizedString("Indent", comment: "") 140 | case .outdent: return NSLocalizedString("Outdent", comment: "") 141 | case .orderedList: return NSLocalizedString("Ordered List", comment: "") 142 | case .unorderedList: return NSLocalizedString("Unordered List", comment: "") 143 | case .alignLeft: return NSLocalizedString("Left", comment: "") 144 | case .alignCenter: return NSLocalizedString("Center", comment: "") 145 | case .alignRight: return NSLocalizedString("Right", comment: "") 146 | case .image: return NSLocalizedString("Image", comment: "") 147 | case .link: return NSLocalizedString("Link", comment: "") 148 | } 149 | } 150 | 151 | public func action(_ toolbar: RichEditorToolbar) { 152 | switch self { 153 | case .clear: toolbar.editor?.removeFormat() 154 | case .undo: toolbar.editor?.undo() 155 | case .redo: toolbar.editor?.redo() 156 | case .bold: toolbar.editor?.bold() 157 | case .italic: toolbar.editor?.italic() 158 | case .subscript: toolbar.editor?.subscriptText() 159 | case .superscript: toolbar.editor?.superscript() 160 | case .strike: toolbar.editor?.strikethrough() 161 | case .underline: toolbar.editor?.underline() 162 | case .textColor: toolbar.delegate?.richEditorToolbarChangeTextColor?(toolbar) 163 | case .textBackgroundColor: toolbar.delegate?.richEditorToolbarChangeBackgroundColor?(toolbar) 164 | case .header(let h): toolbar.editor?.header(h) 165 | case .indent: toolbar.editor?.indent() 166 | case .outdent: toolbar.editor?.outdent() 167 | case .orderedList: toolbar.editor?.orderedList() 168 | case .unorderedList: toolbar.editor?.unorderedList() 169 | case .alignLeft: toolbar.editor?.alignLeft() 170 | case .alignCenter: toolbar.editor?.alignCenter() 171 | case .alignRight: toolbar.editor?.alignRight() 172 | case .image: toolbar.delegate?.richEditorToolbarInsertImage?(toolbar) 173 | case .link: toolbar.delegate?.richEditorToolbarInsertLink?(toolbar) 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /RichEditorView/Sources/RichEditorToolbar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RichEditorToolbar.swift 3 | // 4 | // Created by Caesar Wirth on 4/2/15. 5 | // Copyright (c) 2015 Caesar Wirth. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | 10 | /// RichEditorToolbarDelegate is a protocol for the RichEditorToolbar. 11 | /// Used to receive actions that need extra work to perform (eg. display some UI) 12 | @objc public protocol RichEditorToolbarDelegate: AnyObject { 13 | 14 | /// Called when the Text Color toolbar item is pressed. 15 | @objc optional func richEditorToolbarChangeTextColor(_ toolbar: RichEditorToolbar) 16 | 17 | /// Called when the Background Color toolbar item is pressed. 18 | @objc optional func richEditorToolbarChangeBackgroundColor(_ toolbar: RichEditorToolbar) 19 | 20 | /// Called when the Insert Image toolbar item is pressed. 21 | @objc optional func richEditorToolbarInsertImage(_ toolbar: RichEditorToolbar) 22 | 23 | /// Called when the Insert Link toolbar item is pressed. 24 | @objc optional func richEditorToolbarInsertLink(_ toolbar: RichEditorToolbar) 25 | } 26 | 27 | /// RichBarButtonItem is a subclass of UIBarButtonItem that takes a callback as opposed to the target-action pattern 28 | @objcMembers open class RichBarButtonItem: UIBarButtonItem { 29 | open var actionHandler: (() -> Void)? 30 | 31 | public convenience init(image: UIImage? = nil, handler: (() -> Void)? = nil) { 32 | self.init(image: image, style: .plain, target: nil, action: nil) 33 | target = self 34 | action = #selector(RichBarButtonItem.buttonWasTapped) 35 | actionHandler = handler 36 | } 37 | 38 | public convenience init(title: String = "", handler: (() -> Void)? = nil) { 39 | self.init(title: title, style: .plain, target: nil, action: nil) 40 | target = self 41 | action = #selector(RichBarButtonItem.buttonWasTapped) 42 | actionHandler = handler 43 | } 44 | 45 | @objc func buttonWasTapped() { 46 | actionHandler?() 47 | } 48 | } 49 | 50 | /// RichEditorToolbar is UIView that contains the toolbar for actions that can be performed on a RichEditorView 51 | @objcMembers open class RichEditorToolbar: UIView { 52 | 53 | /// The delegate to receive events that cannot be automatically completed 54 | open weak var delegate: RichEditorToolbarDelegate? 55 | 56 | /// A reference to the RichEditorView that it should be performing actions on 57 | open weak var editor: RichEditorView? 58 | 59 | /// The list of options to be displayed on the toolbar 60 | open var options: [RichEditorOption] = [] { 61 | didSet { 62 | updateToolbar() 63 | } 64 | } 65 | 66 | /// The tint color to apply to the toolbar background. 67 | open var barTintColor: UIColor? { 68 | get { return backgroundToolbar.barTintColor } 69 | set { backgroundToolbar.barTintColor = newValue } 70 | } 71 | 72 | private var toolbarScroll: UIScrollView 73 | private var toolbar: UIToolbar 74 | private var backgroundToolbar: UIToolbar 75 | 76 | public override init(frame: CGRect) { 77 | toolbarScroll = UIScrollView() 78 | toolbar = UIToolbar() 79 | backgroundToolbar = UIToolbar() 80 | super.init(frame: frame) 81 | setup() 82 | } 83 | 84 | public required init?(coder aDecoder: NSCoder) { 85 | toolbarScroll = UIScrollView() 86 | toolbar = UIToolbar() 87 | backgroundToolbar = UIToolbar() 88 | super.init(coder: aDecoder) 89 | setup() 90 | } 91 | 92 | private func setup() { 93 | autoresizingMask = .flexibleWidth 94 | backgroundColor = .clear 95 | 96 | backgroundToolbar.frame = bounds 97 | backgroundToolbar.autoresizingMask = [.flexibleHeight, .flexibleWidth] 98 | 99 | toolbar.autoresizingMask = .flexibleWidth 100 | toolbar.backgroundColor = .clear 101 | toolbar.setBackgroundImage(UIImage(), forToolbarPosition: .any, barMetrics: .default) 102 | toolbar.setShadowImage(UIImage(), forToolbarPosition: .any) 103 | 104 | toolbarScroll.frame = bounds 105 | toolbarScroll.autoresizingMask = [.flexibleHeight, .flexibleWidth] 106 | toolbarScroll.showsHorizontalScrollIndicator = false 107 | toolbarScroll.showsVerticalScrollIndicator = false 108 | toolbarScroll.backgroundColor = .clear 109 | 110 | toolbarScroll.addSubview(toolbar) 111 | 112 | addSubview(backgroundToolbar) 113 | addSubview(toolbarScroll) 114 | updateToolbar() 115 | } 116 | 117 | private func updateToolbar() { 118 | var buttons = [UIBarButtonItem]() 119 | for option in options { 120 | let handler = { [weak self] in 121 | if let strongSelf = self { 122 | option.action(strongSelf) 123 | } 124 | } 125 | 126 | if let image = option.image { 127 | let button = RichBarButtonItem(image: image, handler: handler) 128 | buttons.append(button) 129 | } else { 130 | let title = option.title 131 | let button = RichBarButtonItem(title: title, handler: handler) 132 | buttons.append(button) 133 | } 134 | } 135 | toolbar.items = buttons 136 | 137 | let defaultIconWidth: CGFloat = 28 138 | let barButtonItemMargin: CGFloat = 12 139 | let width: CGFloat = buttons.reduce(0) {sofar, new in 140 | if let view = new.value(forKey: "view") as? UIView { 141 | return sofar + view.frame.size.width + barButtonItemMargin 142 | } else { 143 | return sofar + (defaultIconWidth + barButtonItemMargin) 144 | } 145 | } 146 | 147 | if width < frame.size.width { 148 | toolbar.frame.size.width = frame.size.width + barButtonItemMargin 149 | } else { 150 | toolbar.frame.size.width = width + barButtonItemMargin 151 | } 152 | toolbar.frame.size.height = 44 153 | toolbarScroll.contentSize.width = width 154 | } 155 | 156 | } 157 | -------------------------------------------------------------------------------- /RichEditorView/Sources/RichEditorView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RichEditor.swift 3 | // 4 | // Created by Caesar Wirth on 4/1/15. 5 | // Copyright (c) 2015 Caesar Wirth. All rights reserved. 6 | // 7 | 8 | import UIKit 9 | import WebKit 10 | 11 | /// The value we hold in order to be able to set the line height before the JS completely loads. 12 | private let DefaultInnerLineHeight: Int = 21 13 | 14 | /// RichEditorDelegate defines callbacks for the delegate of the RichEditorView 15 | @objc public protocol RichEditorDelegate: AnyObject { 16 | /// Called when the inner height of the text being displayed changes 17 | /// Can be used to update the UI 18 | @objc optional func richEditor(_ editor: RichEditorView, heightDidChange height: Int) 19 | 20 | /// Called whenever the content inside the view changes 21 | @objc optional func richEditor(_ editor: RichEditorView, contentDidChange content: String) 22 | 23 | /// Called when the rich editor starts editing 24 | @objc optional func richEditorTookFocusAt(_ editor: RichEditorView, at: CGPoint) 25 | 26 | /// Called when the rich editor starts editing 27 | @objc optional func richEditorTookFocus(_ editor: RichEditorView) 28 | 29 | /// Called when the rich editor stops editing or loses focus 30 | @objc optional func richEditorLostFocus(_ editor: RichEditorView) 31 | 32 | /// Called when the RichEditorView has become ready to receive input 33 | /// More concretely, is called when the internal WKWebView loads for the first time, and contentHTML is set 34 | @objc optional func richEditorDidLoad(_ editor: RichEditorView) 35 | 36 | /// Called when the internal WKWebView begins loading a URL that it does not know how to respond to 37 | /// For example, if there is an external link, and then the user taps it 38 | @objc optional func richEditor(_ editor: RichEditorView, shouldInteractWith url: URL) -> Bool 39 | 40 | /// Called when custom actions are called by callbacks in the JS 41 | /// By default, this method is not used unless called by some custom JS that you add 42 | @objc optional func richEditor(_ editor: RichEditorView, handle action: String) 43 | } 44 | 45 | /// RichEditorView is a UIView that displays richly styled text, and allows it to be edited in a WYSIWYG fashion. 46 | @objcMembers open class RichEditorView: UIView, UIScrollViewDelegate, WKNavigationDelegate, UIGestureRecognizerDelegate { 47 | /// The delegate that will receive callbacks when certain actions are completed. 48 | open weak var delegate: RichEditorDelegate? 49 | 50 | /// Input accessory view to display over they keyboard. 51 | /// Defaults to nil 52 | open override var inputAccessoryView: UIView? { 53 | get { return webView.accessoryView } 54 | set { webView.accessoryView = newValue } 55 | } 56 | 57 | /// The internal WKWebView that is used to display the text. 58 | open private(set) var webView: RichEditorWebView 59 | 60 | /// Whether or not scroll is enabled on the view. 61 | open var isScrollEnabled: Bool = true { 62 | didSet { 63 | webView.scrollView.isScrollEnabled = isScrollEnabled 64 | } 65 | } 66 | 67 | /// Whether or not to allow user input in the view. 68 | open var editingEnabled: Bool = false { 69 | didSet { contentEditable = editingEnabled } 70 | } 71 | 72 | /// The content HTML of the text being displayed. 73 | /// Is continually updated as the text is being edited. 74 | open private(set) var contentHTML: String = "" { 75 | didSet { 76 | delegate?.richEditor?(self, contentDidChange: contentHTML) 77 | } 78 | } 79 | 80 | /// The internal height of the text being displayed. 81 | /// Is continually being updated as the text is edited. 82 | open private(set) var editorHeight: Int = 0 { 83 | didSet { 84 | delegate?.richEditor?(self, heightDidChange: editorHeight) 85 | } 86 | } 87 | 88 | /// The line height of the editor. Defaults to 21. 89 | open private(set) var lineHeight: Int = DefaultInnerLineHeight { 90 | didSet { 91 | runJS("RE.setLineHeight('\(lineHeight)px')") 92 | } 93 | } 94 | 95 | /// Whether or not the editor has finished loading or not yet. 96 | private var isEditorLoaded = false 97 | 98 | /// Value that stores whether or not the content should be editable when the editor is loaded. 99 | /// Is basically `isEditingEnabled` before the editor is loaded. 100 | private var editingEnabledVar = true 101 | 102 | /// The HTML that is currently loaded in the editor view, if it is loaded. If it has not been loaded yet, it is the 103 | /// HTML that will be loaded into the editor view once it finishes initializing. 104 | public var html: String = "" { 105 | didSet { 106 | setHTML(html) 107 | } 108 | } 109 | 110 | /// Private variable that holds the placeholder text, so you can set the placeholder before the editor loads. 111 | private var placeholderText: String = "" 112 | /// The placeholder text that should be shown when there is no user input. 113 | open var placeholder: String { 114 | get { return placeholderText } 115 | set { 116 | placeholderText = newValue 117 | if isEditorLoaded { 118 | runJS("RE.setPlaceholderText('\(newValue.escaped)')") 119 | } 120 | } 121 | } 122 | 123 | // MARK: Initialization 124 | 125 | public override init(frame: CGRect) { 126 | webView = RichEditorWebView() 127 | super.init(frame: frame) 128 | setup() 129 | } 130 | 131 | required public init?(coder aDecoder: NSCoder) { 132 | webView = RichEditorWebView() 133 | super.init(coder: aDecoder) 134 | setup() 135 | } 136 | 137 | private func setup() { 138 | // configure webview 139 | webView.frame = bounds 140 | webView.navigationDelegate = self 141 | webView.autoresizingMask = [.flexibleWidth, .flexibleHeight] 142 | if #available(iOS 10.0, *) { 143 | webView.configuration.dataDetectorTypes = WKDataDetectorTypes() 144 | } 145 | webView.scrollView.isScrollEnabled = isScrollEnabled 146 | webView.scrollView.bounces = true 147 | webView.scrollView.delegate = self 148 | webView.scrollView.clipsToBounds = false 149 | addSubview(webView) 150 | loadRichEditorView() 151 | } 152 | 153 | private func loadRichEditorView() { 154 | let bundle: Bundle 155 | #if SWIFT_PACKAGE 156 | bundle = Bundle.module 157 | #else 158 | bundle = Bundle(for: RichEditorView.self) 159 | #endif 160 | if let filePath = bundle.path(forResource: "rich_editor", ofType: "html") { 161 | let url = URL(fileURLWithPath: filePath, isDirectory: false) 162 | webView.loadFileURL( 163 | url, 164 | allowingReadAccessTo: url.deletingLastPathComponent() 165 | ) 166 | return 167 | } 168 | fatalError("Failed to load rich_editor.html, check your dependency configuration") 169 | } 170 | 171 | // MARK: - Rich Text Editing 172 | 173 | open func isEditingEnabled(handler: @escaping (Bool) -> Void) { 174 | isContentEditable(handler: handler) 175 | } 176 | 177 | private func getLineHeight(handler: @escaping (Int) -> Void) { 178 | if isEditorLoaded { 179 | runJS("RE.getLineHeight()") { r in 180 | if let r = Int(r) { 181 | handler(r) 182 | } else { 183 | handler(DefaultInnerLineHeight) 184 | } 185 | } 186 | } else { 187 | handler(DefaultInnerLineHeight) 188 | } 189 | } 190 | 191 | private func setHTML(_ value: String) { 192 | if isEditorLoaded { 193 | runJS("RE.setHtml('\(value.escaped)')") { _ in 194 | self.updateHeight() 195 | } 196 | } 197 | } 198 | 199 | /// The inner height of the editor div. 200 | /// Fetches it from JS every time, so might be slow! 201 | private func getClientHeight(handler: @escaping (Int) -> Void) { 202 | runJS("document.getElementById('editor').clientHeight") { r in 203 | if let r = Int(r) { 204 | handler(r) 205 | } else { 206 | handler(0) 207 | } 208 | } 209 | } 210 | 211 | public func getHtml(handler: @escaping (String) -> Void) { 212 | runJS("RE.getHtml()") { r in 213 | handler(r) 214 | } 215 | } 216 | 217 | /// Text representation of the data that has been input into the editor view, if it has been loaded. 218 | public func getText(handler: @escaping (String) -> Void) { 219 | runJS("RE.getText()") { r in 220 | handler(r) 221 | } 222 | } 223 | 224 | /// The href of the current selection, if the current selection's parent is an anchor tag. 225 | /// Will be nil if there is no href, or it is an empty string. 226 | public func getSelectedHref(handler: @escaping (String?) -> Void) { 227 | hasRangeSelection(handler: { r in 228 | if !r { 229 | handler(nil) 230 | return 231 | } 232 | self.runJS("RE.getSelectedHref()") { r in 233 | if r == "" { 234 | handler(nil) 235 | } else { 236 | handler(r) 237 | } 238 | } 239 | }) 240 | } 241 | 242 | /// Whether or not the selection has a type specifically of "Range". 243 | public func hasRangeSelection(handler: @escaping (Bool) -> Void) { 244 | runJS("RE.rangeSelectionExists()") { r in 245 | handler(r == "true" ? true : false) 246 | } 247 | } 248 | 249 | /// Whether or not the selection has a type specifically of "Range" or "Caret". 250 | public func hasRangeOrCaretSelection(handler: @escaping (Bool) -> Void) { 251 | runJS("RE.rangeOrCaretSelectionExists()") { r in 252 | handler(r == "true" ? true : false) 253 | } 254 | } 255 | 256 | // MARK: Methods 257 | 258 | public func removeFormat() { 259 | runJS("RE.removeFormat()") 260 | } 261 | 262 | public func setFontSize(_ size: Int) { 263 | runJS("RE.setFontSize('\(size)px')") 264 | } 265 | 266 | public func setEditorBackgroundColor(_ color: UIColor) { 267 | runJS("RE.setBackgroundColor('\(color.hex)')") 268 | } 269 | 270 | public func undo() { 271 | runJS("RE.undo()") 272 | } 273 | 274 | public func redo() { 275 | runJS("RE.redo()") 276 | } 277 | 278 | public func bold() { 279 | runJS("RE.setBold()") 280 | } 281 | 282 | public func italic() { 283 | runJS("RE.setItalic()") 284 | } 285 | 286 | // "superscript" is a keyword 287 | public func subscriptText() { 288 | runJS("RE.setSubscript()") 289 | } 290 | 291 | public func superscript() { 292 | runJS("RE.setSuperscript()") 293 | } 294 | 295 | public func strikethrough() { 296 | runJS("RE.setStrikeThrough()") 297 | } 298 | 299 | public func underline() { 300 | runJS("RE.setUnderline()") 301 | } 302 | 303 | public func setTextColor(_ color: UIColor) { 304 | runJS("RE.prepareInsert()") 305 | runJS("RE.setTextColor('\(color.hex)')") 306 | } 307 | 308 | public func setEditorFontColor(_ color: UIColor) { 309 | runJS("RE.setBaseTextColor('\(color.hex)')") 310 | } 311 | 312 | public func setTextBackgroundColor(_ color: UIColor) { 313 | runJS("RE.prepareInsert()") 314 | runJS("RE.setTextBackgroundColor('\(color.hex)')") 315 | } 316 | 317 | public func header(_ h: Int) { 318 | runJS("RE.setHeading('\(h)')") 319 | } 320 | 321 | public func indent() { 322 | runJS("RE.setIndent()") 323 | } 324 | 325 | public func outdent() { 326 | runJS("RE.setOutdent()") 327 | } 328 | 329 | public func orderedList() { 330 | runJS("RE.setOrderedList()") 331 | } 332 | 333 | public func unorderedList() { 334 | runJS("RE.setUnorderedList()") 335 | } 336 | 337 | public func blockquote() { 338 | runJS("RE.setBlockquote()"); 339 | } 340 | 341 | public func alignLeft() { 342 | runJS("RE.setJustifyLeft()") 343 | } 344 | 345 | public func alignCenter() { 346 | runJS("RE.setJustifyCenter()") 347 | } 348 | 349 | public func alignRight() { 350 | runJS("RE.setJustifyRight()") 351 | } 352 | 353 | public func insertImage(_ url: String, alt: String) { 354 | runJS("RE.prepareInsert()") 355 | runJS("RE.insertImage('\(url.escaped)', '\(alt.escaped)')") 356 | } 357 | 358 | public func insertLink(_ href: String, title: String) { 359 | runJS("RE.prepareInsert()") 360 | runJS("RE.insertLink('\(href.escaped)', '\(title.escaped)')") 361 | } 362 | 363 | public func focus() { 364 | runJS("RE.focus()") 365 | } 366 | 367 | public func focus(at: CGPoint) { 368 | delegate?.richEditorTookFocusAt?(self, at: at) 369 | runJS("RE.focusAtPoint(\(at.x), \(at.y))") 370 | } 371 | 372 | public func blur() { 373 | runJS("RE.blurFocus()") 374 | } 375 | 376 | /// Runs some JavaScript on the WKWebView and returns the result 377 | /// If there is no result, returns an empty string 378 | /// - parameter js: The JavaScript string to be run 379 | /// - returns: The result of the JavaScript that was run 380 | public func runJS(_ js: String, handler: ((String) -> Void)? = nil) { 381 | webView.evaluateJavaScript(js) { (result, error) in 382 | if let error = error { 383 | print("WKWebViewJavascriptBridge Error: \(String(describing: error)) - JS: \(js)") 384 | handler?("") 385 | return 386 | } 387 | 388 | guard let handler = handler else { 389 | return 390 | } 391 | 392 | if let resultInt = result as? Int { 393 | handler("\(resultInt)") 394 | return 395 | } 396 | 397 | if let resultBool = result as? Bool { 398 | handler(resultBool ? "true" : "false") 399 | return 400 | } 401 | 402 | if let resultStr = result as? String { 403 | handler(resultStr) 404 | return 405 | } 406 | 407 | // no result 408 | handler("") 409 | } 410 | } 411 | 412 | // MARK: - Delegate Methods 413 | 414 | // MARK: UIScrollViewDelegate 415 | 416 | public func scrollViewDidScroll(_ scrollView: UIScrollView) { 417 | // We use this to keep the scroll view from changing its offset when the keyboard comes up 418 | if !isScrollEnabled { 419 | scrollView.bounds = webView.bounds 420 | } 421 | } 422 | 423 | // MARK: WKWebViewDelegate 424 | 425 | public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { 426 | // empy 427 | } 428 | 429 | public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { 430 | // Handle pre-defined editor actions 431 | let callbackPrefix = "re-callback://" 432 | if navigationAction.request.url?.absoluteString.hasPrefix(callbackPrefix) == true { 433 | // When we get a callback, we need to fetch the command queue to run the commands 434 | // It comes in as a JSON array of commands that we need to parse 435 | runJS("RE.getCommandQueue()") { commands in 436 | if let data = commands.data(using: .utf8) { 437 | 438 | let jsonCommands: [String] 439 | do { 440 | jsonCommands = try JSONSerialization.jsonObject(with: data) as? [String] ?? [] 441 | } catch { 442 | jsonCommands = [] 443 | NSLog("RichEditorView: Failed to parse JSON Commands") 444 | } 445 | 446 | jsonCommands.forEach(self.performCommand) 447 | } 448 | } 449 | return decisionHandler(WKNavigationActionPolicy.cancel); 450 | } 451 | 452 | // User is tapping on a link, so we should react accordingly 453 | if navigationAction.navigationType == .linkActivated { 454 | if let url = navigationAction.request.url { 455 | if delegate?.richEditor?(self, shouldInteractWith: url) ?? false { 456 | return decisionHandler(WKNavigationActionPolicy.allow); 457 | } 458 | } 459 | } 460 | 461 | return decisionHandler(WKNavigationActionPolicy.allow); 462 | } 463 | 464 | // MARK: UIGestureRecognizerDelegate 465 | 466 | /// Delegate method for our UITapGestureDelegate. 467 | /// Since the internal web view also has gesture recognizers, we have to make sure that we actually receive our taps. 468 | public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { 469 | return true 470 | } 471 | 472 | // MARK: - Private Implementation Details 473 | 474 | private var contentEditable: Bool = false { 475 | didSet { 476 | editingEnabledVar = contentEditable 477 | if isEditorLoaded { 478 | let value = (contentEditable ? "true" : "false") 479 | runJS("RE.editor.contentEditable = \(value)") 480 | } 481 | } 482 | } 483 | private func isContentEditable(handler: @escaping (Bool) -> Void) { 484 | if isEditorLoaded { 485 | // to get the "editable" value is a different property, than to disable it 486 | // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/contentEditable 487 | runJS("RE.editor.isContentEditable") { value in 488 | self.editingEnabledVar = Bool(value) ?? false 489 | } 490 | } 491 | } 492 | 493 | /// The position of the caret relative to the currently shown content. 494 | /// For example, if the cursor is directly at the top of what is visible, it will return 0. 495 | /// This also means that it will be negative if it is above what is currently visible. 496 | /// Can also return 0 if some sort of error occurs between JS and here. 497 | private func relativeCaretYPosition(handler: @escaping (Int) -> Void) { 498 | runJS("RE.getRelativeCaretYPosition()") { r in 499 | handler(Int(r) ?? 0) 500 | } 501 | } 502 | 503 | private func updateHeight() { 504 | runJS("document.getElementById('editor').clientHeight") { heightString in 505 | let height = Int(heightString) ?? 0 506 | if self.editorHeight != height { 507 | self.editorHeight = height 508 | } 509 | } 510 | } 511 | 512 | /// Scrolls the editor to a position where the caret is visible. 513 | /// Called repeatedly to make sure the caret is always visible when inputting text. 514 | /// Works only if the `lineHeight` of the editor is available. 515 | private func scrollCaretToVisible() { 516 | let scrollView = self.webView.scrollView 517 | 518 | getClientHeight(handler: { clientHeight in 519 | let contentHeight = clientHeight > 0 ? CGFloat(clientHeight) : scrollView.frame.height 520 | scrollView.contentSize = CGSize(width: scrollView.frame.width, height: contentHeight) 521 | 522 | // XXX: Maybe find a better way to get the cursor height 523 | self.getLineHeight(handler: { lh in 524 | let lineHeight = CGFloat(lh) 525 | let cursorHeight = lineHeight - 4 526 | self.relativeCaretYPosition(handler: { r in 527 | let visiblePosition = CGFloat(r) 528 | var offset: CGPoint? 529 | 530 | if visiblePosition + cursorHeight > scrollView.bounds.size.height { 531 | // Visible caret position goes further than our bounds 532 | offset = CGPoint(x: 0, y: (visiblePosition + lineHeight) - scrollView.bounds.height + scrollView.contentOffset.y) 533 | } else if visiblePosition < 0 { 534 | // Visible caret position is above what is currently visible 535 | var amount = scrollView.contentOffset.y + visiblePosition 536 | amount = amount < 0 ? 0 : amount 537 | offset = CGPoint(x: scrollView.contentOffset.x, y: amount) 538 | } 539 | 540 | if let offset = offset { 541 | scrollView.setContentOffset(offset, animated: true) 542 | } 543 | }) 544 | }) 545 | }) 546 | } 547 | 548 | /// Called when actions are received from JavaScript 549 | /// - parameter method: String with the name of the method and optional parameters that were passed in 550 | private func performCommand(_ method: String) { 551 | if method.hasPrefix("ready") { 552 | // If loading for the first time, we have to set the content HTML to be displayed 553 | if !isEditorLoaded { 554 | isEditorLoaded = true 555 | setHTML(html) 556 | contentHTML = html 557 | contentEditable = editingEnabledVar 558 | placeholder = placeholderText 559 | lineHeight = DefaultInnerLineHeight 560 | 561 | delegate?.richEditorDidLoad?(self) 562 | } 563 | updateHeight() 564 | } 565 | else if method.hasPrefix("input") { 566 | scrollCaretToVisible() 567 | runJS("RE.getHtml()") { content in 568 | self.contentHTML = content 569 | self.updateHeight() 570 | } 571 | } 572 | else if method.hasPrefix("updateHeight") { 573 | updateHeight() 574 | } 575 | else if method.hasPrefix("focus") { 576 | delegate?.richEditorTookFocus?(self) 577 | } 578 | else if method.hasPrefix("blur") { 579 | delegate?.richEditorLostFocus?(self) 580 | } 581 | else if method.hasPrefix("action/") { 582 | runJS("RE.getHtml()") { content in 583 | self.contentHTML = content 584 | 585 | // If there are any custom actions being called 586 | // We need to tell the delegate about it 587 | let actionPrefix = "action/" 588 | let range = method.range(of: actionPrefix)! 589 | let action = method.replacingCharacters(in: range, with: "") 590 | 591 | self.delegate?.richEditor?(self, handle: action) 592 | } 593 | } 594 | } 595 | 596 | // MARK: - Responder Handling 597 | 598 | override open func becomeFirstResponder() -> Bool { 599 | if !webView.isFirstResponder { 600 | focus() 601 | return true 602 | } else { 603 | return false 604 | } 605 | } 606 | 607 | open override func resignFirstResponder() -> Bool { 608 | blur() 609 | return true 610 | } 611 | 612 | } 613 | -------------------------------------------------------------------------------- /RichEditorView/Sources/RichEditorWebView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RichEditorWebView.swift 3 | // RichEditorView 4 | // 5 | // Created by C. Bess on 9/18/19. 6 | // 7 | 8 | import WebKit 9 | 10 | public class RichEditorWebView: WKWebView { 11 | 12 | public var accessoryView: UIView? 13 | 14 | public override var inputAccessoryView: UIView? { 15 | return accessoryView 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /RichEditorView/Sources/String+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+Extensions.swift 3 | // Pods 4 | // 5 | // Created by Caesar Wirth on 10/9/16. 6 | // 7 | // 8 | 9 | import Foundation 10 | 11 | internal extension String { 12 | 13 | /// A string with the ' characters in it escaped. 14 | /// Used when passing a string into JavaScript, so the string is not completed too soon 15 | var escaped: String { 16 | let unicode = self.unicodeScalars 17 | var newString = "" 18 | for char in unicode { 19 | if char.value == 39 || // 39 == ' in ASCII 20 | char.value < 9 || // 9 == horizontal tab in ASCII 21 | (char.value > 9 && char.value < 32) // < 32 == special characters in ASCII 22 | { 23 | let escaped = char.escaped(asASCII: true) 24 | newString.append(escaped) 25 | } else { 26 | newString.append(String(char)) 27 | } 28 | } 29 | return newString 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /RichEditorView/Sources/UIColor+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+Extensions.swift 3 | // Pods 4 | // 5 | // Created by Caesar Wirth on 10/9/16. 6 | // 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | internal extension UIColor { 13 | 14 | /// Hexadecimal representation of the UIColor. 15 | /// For example, UIColor.blackColor() becomes "#000000". 16 | var hex: String { 17 | var red: CGFloat = 0 18 | var green: CGFloat = 0 19 | var blue: CGFloat = 0 20 | self.getRed(&red, green: &green, blue: &blue, alpha: nil) 21 | 22 | let r = Int(255.0 * red) 23 | let g = Int(255.0 * green) 24 | let b = Int(255.0 * blue) 25 | 26 | let str = String(format: "#%02x%02x%02x", r, g, b) 27 | return str 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /RichEditorViewSample/Podfile: -------------------------------------------------------------------------------- 1 | use_frameworks! 2 | 3 | target 'RichEditorViewSample' do 4 | # pod "RichEditorView", :path => "../" 5 | end 6 | -------------------------------------------------------------------------------- /RichEditorViewSample/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODFILE CHECKSUM: 7d87321ef6338e321927e7a7ce29ff437deabfa1 2 | 3 | COCOAPODS: 1.16.2 4 | -------------------------------------------------------------------------------- /RichEditorViewSample/Pods/Manifest.lock: -------------------------------------------------------------------------------- 1 | PODFILE CHECKSUM: 7d87321ef6338e321927e7a7ce29ff437deabfa1 2 | 3 | COCOAPODS: 1.16.2 4 | -------------------------------------------------------------------------------- /RichEditorViewSample/Pods/Pods.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 54; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 28BC374060E0A8BBC8E98D61471D6901 /* Pods-RichEditorViewSample-dummy.m in Sources */ = {isa = PBXBuildFile; fileRef = D6D32675A3BD8B46633D8E38051340C7 /* Pods-RichEditorViewSample-dummy.m */; }; 11 | 31621AAE698DC691DAB7DB9E14C7D06F /* Pods-RichEditorViewSample-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 4B41C2A0B6E193DCDAF56AC248BE92B9 /* Pods-RichEditorViewSample-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; }; 12 | F104968D80AD729BD2D20FA5E9A0BD98 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 384DDA2CB25005BD6479B5987C619DD4 /* Foundation.framework */; }; 13 | /* End PBXBuildFile section */ 14 | 15 | /* Begin PBXFileReference section */ 16 | 0C9D0347381FAECFD296FA7F98AEF3AA /* Pods-RichEditorViewSample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-RichEditorViewSample.release.xcconfig"; sourceTree = ""; }; 17 | 18A8A5EC271AEC1E49F930F77D85A249 /* Pods-RichEditorViewSample */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = "Pods-RichEditorViewSample"; path = Pods_RichEditorViewSample.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 18 | 18D5005CC186C5EE722188A461136227 /* Pods-RichEditorViewSample-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-RichEditorViewSample-Info.plist"; sourceTree = " "; }; 19 | 384DDA2CB25005BD6479B5987C619DD4 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; 20 | 4B41C2A0B6E193DCDAF56AC248BE92B9 /* Pods-RichEditorViewSample-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Pods-RichEditorViewSample-umbrella.h"; sourceTree = " "; }; 21 | 770DBAE8144ECAAD2C1CF977D6C808ED /* Pods-RichEditorViewSample-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-RichEditorViewSample-acknowledgements.markdown"; sourceTree = " "; }; 22 | 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 23 | B32FFE371562124C01AE7A8791A9391E /* Pods-RichEditorViewSample-acknowledgements.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Pods-RichEditorViewSample-acknowledgements.plist"; sourceTree = " "; }; 24 | CEC52D8F6CAB68A3AB3969E45985B60F /* Pods-RichEditorViewSample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-RichEditorViewSample.debug.xcconfig"; sourceTree = " "; }; 25 | D6D32675A3BD8B46633D8E38051340C7 /* Pods-RichEditorViewSample-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Pods-RichEditorViewSample-dummy.m"; sourceTree = " "; }; 26 | E05496FFE4FF2A413D88DB9E6545488C /* Pods-RichEditorViewSample.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = "Pods-RichEditorViewSample.modulemap"; sourceTree = " "; }; 27 | /* End PBXFileReference section */ 28 | 29 | /* Begin PBXFrameworksBuildPhase section */ 30 | 83E36B4A28F71862BC36B1B69C752ABB /* Frameworks */ = { 31 | isa = PBXFrameworksBuildPhase; 32 | buildActionMask = 2147483647; 33 | files = ( 34 | F104968D80AD729BD2D20FA5E9A0BD98 /* Foundation.framework in Frameworks */, 35 | ); 36 | runOnlyForDeploymentPostprocessing = 0; 37 | }; 38 | /* End PBXFrameworksBuildPhase section */ 39 | 40 | /* Begin PBXGroup section */ 41 | 36321F6094B47956475F391A8ED3D14A /* Products */ = { 42 | isa = PBXGroup; 43 | children = ( 44 | 18A8A5EC271AEC1E49F930F77D85A249 /* Pods-RichEditorViewSample */, 45 | ); 46 | name = Products; 47 | sourceTree = " "; 48 | }; 49 | B6D4033A5C25C4C2DE8E9D8A1CC8EB8D /* Pods-RichEditorViewSample */ = { 50 | isa = PBXGroup; 51 | children = ( 52 | E05496FFE4FF2A413D88DB9E6545488C /* Pods-RichEditorViewSample.modulemap */, 53 | 770DBAE8144ECAAD2C1CF977D6C808ED /* Pods-RichEditorViewSample-acknowledgements.markdown */, 54 | B32FFE371562124C01AE7A8791A9391E /* Pods-RichEditorViewSample-acknowledgements.plist */, 55 | D6D32675A3BD8B46633D8E38051340C7 /* Pods-RichEditorViewSample-dummy.m */, 56 | 18D5005CC186C5EE722188A461136227 /* Pods-RichEditorViewSample-Info.plist */, 57 | 4B41C2A0B6E193DCDAF56AC248BE92B9 /* Pods-RichEditorViewSample-umbrella.h */, 58 | CEC52D8F6CAB68A3AB3969E45985B60F /* Pods-RichEditorViewSample.debug.xcconfig */, 59 | 0C9D0347381FAECFD296FA7F98AEF3AA /* Pods-RichEditorViewSample.release.xcconfig */, 60 | ); 61 | name = "Pods-RichEditorViewSample"; 62 | path = "Target Support Files/Pods-RichEditorViewSample"; 63 | sourceTree = " "; 64 | }; 65 | CF1408CF629C7361332E53B88F7BD30C = { 66 | isa = PBXGroup; 67 | children = ( 68 | 9D940727FF8FB9C785EB98E56350EF41 /* Podfile */, 69 | D210D550F4EA176C3123ED886F8F87F5 /* Frameworks */, 70 | 36321F6094B47956475F391A8ED3D14A /* Products */, 71 | E69C244F008941300308F8133FC25A27 /* Targets Support Files */, 72 | ); 73 | sourceTree = " "; 74 | }; 75 | D210D550F4EA176C3123ED886F8F87F5 /* Frameworks */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | E4801F62A6B08CD9B5410329F1A18FDE /* iOS */, 79 | ); 80 | name = Frameworks; 81 | sourceTree = " "; 82 | }; 83 | E4801F62A6B08CD9B5410329F1A18FDE /* iOS */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | 384DDA2CB25005BD6479B5987C619DD4 /* Foundation.framework */, 87 | ); 88 | name = iOS; 89 | sourceTree = " "; 90 | }; 91 | E69C244F008941300308F8133FC25A27 /* Targets Support Files */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | B6D4033A5C25C4C2DE8E9D8A1CC8EB8D /* Pods-RichEditorViewSample */, 95 | ); 96 | name = "Targets Support Files"; 97 | sourceTree = " "; 98 | }; 99 | /* End PBXGroup section */ 100 | 101 | /* Begin PBXHeadersBuildPhase section */ 102 | 4CC9132A0DD1141251610A65E279933E /* Headers */ = { 103 | isa = PBXHeadersBuildPhase; 104 | buildActionMask = 2147483647; 105 | files = ( 106 | 31621AAE698DC691DAB7DB9E14C7D06F /* Pods-RichEditorViewSample-umbrella.h in Headers */, 107 | ); 108 | runOnlyForDeploymentPostprocessing = 0; 109 | }; 110 | /* End PBXHeadersBuildPhase section */ 111 | 112 | /* Begin PBXNativeTarget section */ 113 | 737BA223DB1CCE508AF5096FB998C795 /* Pods-RichEditorViewSample */ = { 114 | isa = PBXNativeTarget; 115 | buildConfigurationList = 83EE2527D5991DCC038CA318B59736C4 /* Build configuration list for PBXNativeTarget "Pods-RichEditorViewSample" */; 116 | buildPhases = ( 117 | 4CC9132A0DD1141251610A65E279933E /* Headers */, 118 | 20182C389705915ADB3A445FEDFE5268 /* Sources */, 119 | 83E36B4A28F71862BC36B1B69C752ABB /* Frameworks */, 120 | 7F392C78973A595A68650452254808B0 /* Resources */, 121 | ); 122 | buildRules = ( 123 | ); 124 | dependencies = ( 125 | ); 126 | name = "Pods-RichEditorViewSample"; 127 | productName = Pods_RichEditorViewSample; 128 | productReference = 18A8A5EC271AEC1E49F930F77D85A249 /* Pods-RichEditorViewSample */; 129 | productType = "com.apple.product-type.framework"; 130 | }; 131 | /* End PBXNativeTarget section */ 132 | 133 | /* Begin PBXProject section */ 134 | BFDFE7DC352907FC980B868725387E98 /* Project object */ = { 135 | isa = PBXProject; 136 | attributes = { 137 | LastSwiftUpdateCheck = 1600; 138 | LastUpgradeCheck = 1600; 139 | }; 140 | buildConfigurationList = 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */; 141 | compatibilityVersion = "Xcode 12.0"; 142 | developmentRegion = en; 143 | hasScannedForEncodings = 0; 144 | knownRegions = ( 145 | Base, 146 | en, 147 | ); 148 | mainGroup = CF1408CF629C7361332E53B88F7BD30C; 149 | minimizedProjectReferenceProxies = 0; 150 | preferredProjectObjectVersion = 77; 151 | productRefGroup = 36321F6094B47956475F391A8ED3D14A /* Products */; 152 | projectDirPath = ""; 153 | projectRoot = ""; 154 | targets = ( 155 | 737BA223DB1CCE508AF5096FB998C795 /* Pods-RichEditorViewSample */, 156 | ); 157 | }; 158 | /* End PBXProject section */ 159 | 160 | /* Begin PBXResourcesBuildPhase section */ 161 | 7F392C78973A595A68650452254808B0 /* Resources */ = { 162 | isa = PBXResourcesBuildPhase; 163 | buildActionMask = 2147483647; 164 | files = ( 165 | ); 166 | runOnlyForDeploymentPostprocessing = 0; 167 | }; 168 | /* End PBXResourcesBuildPhase section */ 169 | 170 | /* Begin PBXSourcesBuildPhase section */ 171 | 20182C389705915ADB3A445FEDFE5268 /* Sources */ = { 172 | isa = PBXSourcesBuildPhase; 173 | buildActionMask = 2147483647; 174 | files = ( 175 | 28BC374060E0A8BBC8E98D61471D6901 /* Pods-RichEditorViewSample-dummy.m in Sources */, 176 | ); 177 | runOnlyForDeploymentPostprocessing = 0; 178 | }; 179 | /* End PBXSourcesBuildPhase section */ 180 | 181 | /* Begin XCBuildConfiguration section */ 182 | 2B9E26EAE2CD392AD762421F663075A1 /* Debug */ = { 183 | isa = XCBuildConfiguration; 184 | buildSettings = { 185 | ALWAYS_SEARCH_USER_PATHS = NO; 186 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 187 | CLANG_ANALYZER_NONNULL = YES; 188 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 189 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 190 | CLANG_CXX_LIBRARY = "libc++"; 191 | CLANG_ENABLE_MODULES = YES; 192 | CLANG_ENABLE_OBJC_ARC = YES; 193 | CLANG_ENABLE_OBJC_WEAK = YES; 194 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 195 | CLANG_WARN_BOOL_CONVERSION = YES; 196 | CLANG_WARN_COMMA = YES; 197 | CLANG_WARN_CONSTANT_CONVERSION = YES; 198 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 199 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 200 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 201 | CLANG_WARN_EMPTY_BODY = YES; 202 | CLANG_WARN_ENUM_CONVERSION = YES; 203 | CLANG_WARN_INFINITE_RECURSION = YES; 204 | CLANG_WARN_INT_CONVERSION = YES; 205 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 206 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 207 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 208 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 209 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 210 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 211 | CLANG_WARN_STRICT_PROTOTYPES = YES; 212 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 213 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 214 | CLANG_WARN_UNREACHABLE_CODE = YES; 215 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 216 | COPY_PHASE_STRIP = NO; 217 | DEBUG_INFORMATION_FORMAT = dwarf; 218 | ENABLE_STRICT_OBJC_MSGSEND = YES; 219 | ENABLE_TESTABILITY = YES; 220 | GCC_C_LANGUAGE_STANDARD = gnu11; 221 | GCC_DYNAMIC_NO_PIC = NO; 222 | GCC_NO_COMMON_BLOCKS = YES; 223 | GCC_OPTIMIZATION_LEVEL = 0; 224 | GCC_PREPROCESSOR_DEFINITIONS = ( 225 | "POD_CONFIGURATION_DEBUG=1", 226 | "DEBUG=1", 227 | "$(inherited)", 228 | ); 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 = 12.0; 236 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 237 | MTL_FAST_MATH = YES; 238 | ONLY_ACTIVE_ARCH = YES; 239 | PRODUCT_NAME = "$(TARGET_NAME)"; 240 | STRIP_INSTALLED_PRODUCT = NO; 241 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 242 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 243 | SWIFT_VERSION = 5.0; 244 | SYMROOT = "${SRCROOT}/../build"; 245 | }; 246 | name = Debug; 247 | }; 248 | 63FAF33E1C55B71A5F5A8B3CC8749F99 /* Release */ = { 249 | isa = XCBuildConfiguration; 250 | buildSettings = { 251 | ALWAYS_SEARCH_USER_PATHS = NO; 252 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 253 | CLANG_ANALYZER_NONNULL = YES; 254 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 255 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 256 | CLANG_CXX_LIBRARY = "libc++"; 257 | CLANG_ENABLE_MODULES = YES; 258 | CLANG_ENABLE_OBJC_ARC = YES; 259 | CLANG_ENABLE_OBJC_WEAK = YES; 260 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 261 | CLANG_WARN_BOOL_CONVERSION = YES; 262 | CLANG_WARN_COMMA = YES; 263 | CLANG_WARN_CONSTANT_CONVERSION = YES; 264 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 265 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 266 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 267 | CLANG_WARN_EMPTY_BODY = YES; 268 | CLANG_WARN_ENUM_CONVERSION = YES; 269 | CLANG_WARN_INFINITE_RECURSION = YES; 270 | CLANG_WARN_INT_CONVERSION = YES; 271 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 272 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 273 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 274 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 275 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 276 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 277 | CLANG_WARN_STRICT_PROTOTYPES = YES; 278 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 279 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 280 | CLANG_WARN_UNREACHABLE_CODE = YES; 281 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 282 | COPY_PHASE_STRIP = NO; 283 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 284 | ENABLE_NS_ASSERTIONS = NO; 285 | ENABLE_STRICT_OBJC_MSGSEND = YES; 286 | GCC_C_LANGUAGE_STANDARD = gnu11; 287 | GCC_NO_COMMON_BLOCKS = YES; 288 | GCC_PREPROCESSOR_DEFINITIONS = ( 289 | "POD_CONFIGURATION_RELEASE=1", 290 | "$(inherited)", 291 | ); 292 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 293 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 294 | GCC_WARN_UNDECLARED_SELECTOR = YES; 295 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 296 | GCC_WARN_UNUSED_FUNCTION = YES; 297 | GCC_WARN_UNUSED_VARIABLE = YES; 298 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 299 | MTL_ENABLE_DEBUG_INFO = NO; 300 | MTL_FAST_MATH = YES; 301 | PRODUCT_NAME = "$(TARGET_NAME)"; 302 | STRIP_INSTALLED_PRODUCT = NO; 303 | SWIFT_COMPILATION_MODE = wholemodule; 304 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 305 | SWIFT_VERSION = 5.0; 306 | SYMROOT = "${SRCROOT}/../build"; 307 | }; 308 | name = Release; 309 | }; 310 | 685FFBEA12A9A88DF6F98BAEAC835E17 /* Release */ = { 311 | isa = XCBuildConfiguration; 312 | baseConfigurationReference = 0C9D0347381FAECFD296FA7F98AEF3AA /* Pods-RichEditorViewSample.release.xcconfig */; 313 | buildSettings = { 314 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; 315 | CLANG_ENABLE_OBJC_WEAK = NO; 316 | "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; 317 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 318 | "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; 319 | CURRENT_PROJECT_VERSION = 1; 320 | DEFINES_MODULE = YES; 321 | DYLIB_COMPATIBILITY_VERSION = 1; 322 | DYLIB_CURRENT_VERSION = 1; 323 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 324 | ENABLE_MODULE_VERIFIER = NO; 325 | ENABLE_USER_SCRIPT_SANDBOXING = NO; 326 | INFOPLIST_FILE = "Target Support Files/Pods-RichEditorViewSample/Pods-RichEditorViewSample-Info.plist"; 327 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 328 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 329 | LD_RUNPATH_SEARCH_PATHS = ( 330 | "$(inherited)", 331 | "@executable_path/Frameworks", 332 | "@loader_path/Frameworks", 333 | ); 334 | MACH_O_TYPE = staticlib; 335 | MODULEMAP_FILE = "Target Support Files/Pods-RichEditorViewSample/Pods-RichEditorViewSample.modulemap"; 336 | OTHER_LDFLAGS = ""; 337 | OTHER_LIBTOOLFLAGS = ""; 338 | PODS_ROOT = "$(SRCROOT)"; 339 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; 340 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 341 | SDKROOT = iphoneos; 342 | SKIP_INSTALL = YES; 343 | TARGETED_DEVICE_FAMILY = "1,2"; 344 | VALIDATE_PRODUCT = YES; 345 | VERSIONING_SYSTEM = "apple-generic"; 346 | VERSION_INFO_PREFIX = ""; 347 | }; 348 | name = Release; 349 | }; 350 | EE0970EBD1219FFA5256E28C4294DD4D /* Debug */ = { 351 | isa = XCBuildConfiguration; 352 | baseConfigurationReference = CEC52D8F6CAB68A3AB3969E45985B60F /* Pods-RichEditorViewSample.debug.xcconfig */; 353 | buildSettings = { 354 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO; 355 | CLANG_ENABLE_OBJC_WEAK = NO; 356 | "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; 357 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 358 | "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; 359 | CURRENT_PROJECT_VERSION = 1; 360 | DEFINES_MODULE = YES; 361 | DYLIB_COMPATIBILITY_VERSION = 1; 362 | DYLIB_CURRENT_VERSION = 1; 363 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 364 | ENABLE_MODULE_VERIFIER = NO; 365 | ENABLE_USER_SCRIPT_SANDBOXING = NO; 366 | INFOPLIST_FILE = "Target Support Files/Pods-RichEditorViewSample/Pods-RichEditorViewSample-Info.plist"; 367 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 368 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 369 | LD_RUNPATH_SEARCH_PATHS = ( 370 | "$(inherited)", 371 | "@executable_path/Frameworks", 372 | "@loader_path/Frameworks", 373 | ); 374 | MACH_O_TYPE = staticlib; 375 | MODULEMAP_FILE = "Target Support Files/Pods-RichEditorViewSample/Pods-RichEditorViewSample.modulemap"; 376 | OTHER_LDFLAGS = ""; 377 | OTHER_LIBTOOLFLAGS = ""; 378 | PODS_ROOT = "$(SRCROOT)"; 379 | PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.${PRODUCT_NAME:rfc1034identifier}"; 380 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 381 | SDKROOT = iphoneos; 382 | SKIP_INSTALL = YES; 383 | TARGETED_DEVICE_FAMILY = "1,2"; 384 | VERSIONING_SYSTEM = "apple-generic"; 385 | VERSION_INFO_PREFIX = ""; 386 | }; 387 | name = Debug; 388 | }; 389 | /* End XCBuildConfiguration section */ 390 | 391 | /* Begin XCConfigurationList section */ 392 | 4821239608C13582E20E6DA73FD5F1F9 /* Build configuration list for PBXProject "Pods" */ = { 393 | isa = XCConfigurationList; 394 | buildConfigurations = ( 395 | 2B9E26EAE2CD392AD762421F663075A1 /* Debug */, 396 | 63FAF33E1C55B71A5F5A8B3CC8749F99 /* Release */, 397 | ); 398 | defaultConfigurationIsVisible = 0; 399 | defaultConfigurationName = Release; 400 | }; 401 | 83EE2527D5991DCC038CA318B59736C4 /* Build configuration list for PBXNativeTarget "Pods-RichEditorViewSample" */ = { 402 | isa = XCConfigurationList; 403 | buildConfigurations = ( 404 | EE0970EBD1219FFA5256E28C4294DD4D /* Debug */, 405 | 685FFBEA12A9A88DF6F98BAEAC835E17 /* Release */, 406 | ); 407 | defaultConfigurationIsVisible = 0; 408 | defaultConfigurationName = Release; 409 | }; 410 | /* End XCConfigurationList section */ 411 | }; 412 | rootObject = BFDFE7DC352907FC980B868725387E98 /* Project object */; 413 | } 414 | -------------------------------------------------------------------------------- /RichEditorViewSample/Pods/Target Support Files/Pods-RichEditorViewSample/Pods-RichEditorViewSample-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 27 | -------------------------------------------------------------------------------- /RichEditorViewSample/Pods/Target Support Files/Pods-RichEditorViewSample/Pods-RichEditorViewSample-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | Generated by CocoaPods - https://cocoapods.org 4 | -------------------------------------------------------------------------------- /RichEditorViewSample/Pods/Target Support Files/Pods-RichEditorViewSample/Pods-RichEditorViewSample-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 |5 | 26 |CFBundleDevelopmentRegion 6 |${PODS_DEVELOPMENT_LANGUAGE} 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.0 19 |CFBundleSignature 20 |???? 21 |CFBundleVersion 22 |${CURRENT_PROJECT_VERSION} 23 |NSPrincipalClass 24 |25 | 4 | 30 | -------------------------------------------------------------------------------- /RichEditorViewSample/Pods/Target Support Files/Pods-RichEditorViewSample/Pods-RichEditorViewSample-dummy.m: -------------------------------------------------------------------------------- 1 | #import5 | 29 |PreferenceSpecifiers 6 |7 | 24 |8 | 15 |FooterText 9 |This application makes use of the following third party libraries: 10 |Title 11 |Acknowledgements 12 |Type 13 |PSGroupSpecifier 14 |16 | 23 |FooterText 17 |Generated by CocoaPods - https://cocoapods.org 18 |Title 19 |20 | Type 21 |PSGroupSpecifier 22 |StringsTable 25 |Acknowledgements 26 |Title 27 |Acknowledgements 28 |2 | @interface PodsDummy_Pods_RichEditorViewSample : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_RichEditorViewSample 5 | @end 6 | -------------------------------------------------------------------------------- /RichEditorViewSample/Pods/Target Support Files/Pods-RichEditorViewSample/Pods-RichEditorViewSample-frameworks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | set -u 4 | set -o pipefail 5 | 6 | function on_error { 7 | echo "$(realpath -mq "${0}"):$1: error: Unexpected failure" 8 | } 9 | trap 'on_error $LINENO' ERR 10 | 11 | if [ -z ${FRAMEWORKS_FOLDER_PATH+x} ]; then 12 | # If FRAMEWORKS_FOLDER_PATH is not set, then there's nowhere for us to copy 13 | # frameworks to, so exit 0 (signalling the script phase was successful). 14 | exit 0 15 | fi 16 | 17 | echo "mkdir -p ${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 18 | mkdir -p "${CONFIGURATION_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 19 | 20 | COCOAPODS_PARALLEL_CODE_SIGN="${COCOAPODS_PARALLEL_CODE_SIGN:-false}" 21 | SWIFT_STDLIB_PATH="${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" 22 | BCSYMBOLMAP_DIR="BCSymbolMaps" 23 | 24 | 25 | # This protects against multiple targets copying the same framework dependency at the same time. The solution 26 | # was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html 27 | RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????") 28 | 29 | # Copies and strips a vendored framework 30 | install_framework() 31 | { 32 | if [ -r "${BUILT_PRODUCTS_DIR}/$1" ]; then 33 | local source="${BUILT_PRODUCTS_DIR}/$1" 34 | elif [ -r "${BUILT_PRODUCTS_DIR}/$(basename "$1")" ]; then 35 | local source="${BUILT_PRODUCTS_DIR}/$(basename "$1")" 36 | elif [ -r "$1" ]; then 37 | local source="$1" 38 | fi 39 | 40 | local destination="${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" 41 | 42 | if [ -L "${source}" ]; then 43 | echo "Symlinked..." 44 | source="$(readlink -f "${source}")" 45 | fi 46 | 47 | if [ -d "${source}/${BCSYMBOLMAP_DIR}" ]; then 48 | # Locate and install any .bcsymbolmaps if present, and remove them from the .framework before the framework is copied 49 | find "${source}/${BCSYMBOLMAP_DIR}" -name "*.bcsymbolmap"|while read f; do 50 | echo "Installing $f" 51 | install_bcsymbolmap "$f" "$destination" 52 | rm "$f" 53 | done 54 | rmdir "${source}/${BCSYMBOLMAP_DIR}" 55 | fi 56 | 57 | # Use filter instead of exclude so missing patterns don't throw errors. 58 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${destination}\"" 59 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${destination}" 60 | 61 | local basename 62 | basename="$(basename -s .framework "$1")" 63 | binary="${destination}/${basename}.framework/${basename}" 64 | 65 | if ! [ -r "$binary" ]; then 66 | binary="${destination}/${basename}" 67 | elif [ -L "${binary}" ]; then 68 | echo "Destination binary is symlinked..." 69 | dirname="$(dirname "${binary}")" 70 | binary="${dirname}/$(readlink "${binary}")" 71 | fi 72 | 73 | # Strip invalid architectures so "fat" simulator / device frameworks work on device 74 | if [[ "$(file "$binary")" == *"dynamically linked shared library"* ]]; then 75 | strip_invalid_archs "$binary" 76 | fi 77 | 78 | # Resign the code if required by the build settings to avoid unstable apps 79 | code_sign_if_enabled "${destination}/$(basename "$1")" 80 | 81 | # Embed linked Swift runtime libraries. No longer necessary as of Xcode 7. 82 | if [ "${XCODE_VERSION_MAJOR}" -lt 7 ]; then 83 | local swift_runtime_libs 84 | swift_runtime_libs=$(xcrun otool -LX "$binary" | grep --color=never @rpath/libswift | sed -E s/@rpath\\/\(.+dylib\).*/\\1/g | uniq -u) 85 | for lib in $swift_runtime_libs; do 86 | echo "rsync -auv \"${SWIFT_STDLIB_PATH}/${lib}\" \"${destination}\"" 87 | rsync -auv "${SWIFT_STDLIB_PATH}/${lib}" "${destination}" 88 | code_sign_if_enabled "${destination}/${lib}" 89 | done 90 | fi 91 | } 92 | # Copies and strips a vendored dSYM 93 | install_dsym() { 94 | local source="$1" 95 | warn_missing_arch=${2:-true} 96 | if [ -r "$source" ]; then 97 | # Copy the dSYM into the targets temp dir. 98 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${source}\" \"${DERIVED_FILES_DIR}\"" 99 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${source}" "${DERIVED_FILES_DIR}" 100 | 101 | local basename 102 | basename="$(basename -s .dSYM "$source")" 103 | binary_name="$(ls "$source/Contents/Resources/DWARF")" 104 | binary="${DERIVED_FILES_DIR}/${basename}.dSYM/Contents/Resources/DWARF/${binary_name}" 105 | 106 | # Strip invalid architectures from the dSYM. 107 | if [[ "$(file "$binary")" == *"Mach-O "*"dSYM companion"* ]]; then 108 | strip_invalid_archs "$binary" "$warn_missing_arch" 109 | fi 110 | if [[ $STRIP_BINARY_RETVAL == 0 ]]; then 111 | # Move the stripped file into its final destination. 112 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter \"- CVS/\" --filter \"- .svn/\" --filter \"- .git/\" --filter \"- .hg/\" --filter \"- Headers\" --filter \"- PrivateHeaders\" --filter \"- Modules\" \"${DERIVED_FILES_DIR}/${basename}.framework.dSYM\" \"${DWARF_DSYM_FOLDER_PATH}\"" 113 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --links --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${DERIVED_FILES_DIR}/${basename}.dSYM" "${DWARF_DSYM_FOLDER_PATH}" 114 | else 115 | # The dSYM was not stripped at all, in this case touch a fake folder so the input/output paths from Xcode do not reexecute this script because the file is missing. 116 | mkdir -p "${DWARF_DSYM_FOLDER_PATH}" 117 | touch "${DWARF_DSYM_FOLDER_PATH}/${basename}.dSYM" 118 | fi 119 | fi 120 | } 121 | 122 | # Used as a return value for each invocation of `strip_invalid_archs` function. 123 | STRIP_BINARY_RETVAL=0 124 | 125 | # Strip invalid architectures 126 | strip_invalid_archs() { 127 | binary="$1" 128 | warn_missing_arch=${2:-true} 129 | # Get architectures for current target binary 130 | binary_archs="$(lipo -info "$binary" | rev | cut -d ':' -f1 | awk '{$1=$1;print}' | rev)" 131 | # Intersect them with the architectures we are building for 132 | intersected_archs="$(echo ${ARCHS[@]} ${binary_archs[@]} | tr ' ' '\n' | sort | uniq -d)" 133 | # If there are no archs supported by this binary then warn the user 134 | if [[ -z "$intersected_archs" ]]; then 135 | if [[ "$warn_missing_arch" == "true" ]]; then 136 | echo "warning: [CP] Vendored binary '$binary' contains architectures ($binary_archs) none of which match the current build architectures ($ARCHS)." 137 | fi 138 | STRIP_BINARY_RETVAL=1 139 | return 140 | fi 141 | stripped="" 142 | for arch in $binary_archs; do 143 | if ! [[ "${ARCHS}" == *"$arch"* ]]; then 144 | # Strip non-valid architectures in-place 145 | lipo -remove "$arch" -output "$binary" "$binary" 146 | stripped="$stripped $arch" 147 | fi 148 | done 149 | if [[ "$stripped" ]]; then 150 | echo "Stripped $binary of architectures:$stripped" 151 | fi 152 | STRIP_BINARY_RETVAL=0 153 | } 154 | 155 | # Copies the bcsymbolmap files of a vendored framework 156 | install_bcsymbolmap() { 157 | local bcsymbolmap_path="$1" 158 | local destination="${BUILT_PRODUCTS_DIR}" 159 | echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}"" 160 | rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" --filter "- CVS/" --filter "- .svn/" --filter "- .git/" --filter "- .hg/" --filter "- Headers" --filter "- PrivateHeaders" --filter "- Modules" "${bcsymbolmap_path}" "${destination}" 161 | } 162 | 163 | # Signs a framework with the provided identity 164 | code_sign_if_enabled() { 165 | if [ -n "${EXPANDED_CODE_SIGN_IDENTITY:-}" -a "${CODE_SIGNING_REQUIRED:-}" != "NO" -a "${CODE_SIGNING_ALLOWED}" != "NO" ]; then 166 | # Use the current code_sign_identity 167 | echo "Code Signing $1 with Identity ${EXPANDED_CODE_SIGN_IDENTITY_NAME}" 168 | local code_sign_cmd="/usr/bin/codesign --force --sign ${EXPANDED_CODE_SIGN_IDENTITY} ${OTHER_CODE_SIGN_FLAGS:-} --preserve-metadata=identifier,entitlements '$1'" 169 | 170 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 171 | code_sign_cmd="$code_sign_cmd &" 172 | fi 173 | echo "$code_sign_cmd" 174 | eval "$code_sign_cmd" 175 | fi 176 | } 177 | 178 | if [[ "$CONFIGURATION" == "Debug" ]]; then 179 | install_framework "${BUILT_PRODUCTS_DIR}/RichEditorView/RichEditorView.framework" 180 | fi 181 | if [[ "$CONFIGURATION" == "Release" ]]; then 182 | install_framework "${BUILT_PRODUCTS_DIR}/RichEditorView/RichEditorView.framework" 183 | fi 184 | if [ "${COCOAPODS_PARALLEL_CODE_SIGN}" == "true" ]; then 185 | wait 186 | fi 187 | -------------------------------------------------------------------------------- /RichEditorViewSample/Pods/Target Support Files/Pods-RichEditorViewSample/Pods-RichEditorViewSample-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_RichEditorViewSampleVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_RichEditorViewSampleVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /RichEditorViewSample/Pods/Target Support Files/Pods-RichEditorViewSample/Pods-RichEditorViewSample.debug.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | PODS_BUILD_DIR = ${BUILD_DIR} 4 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 5 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 6 | PODS_ROOT = ${SRCROOT}/Pods 7 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 8 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 9 | -------------------------------------------------------------------------------- /RichEditorViewSample/Pods/Target Support Files/Pods-RichEditorViewSample/Pods-RichEditorViewSample.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_RichEditorViewSample { 2 | umbrella header "Pods-RichEditorViewSample-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /RichEditorViewSample/Pods/Target Support Files/Pods-RichEditorViewSample/Pods-RichEditorViewSample.release.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 3 | PODS_BUILD_DIR = ${BUILD_DIR} 4 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 5 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 6 | PODS_ROOT = ${SRCROOT}/Pods 7 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 8 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 9 | -------------------------------------------------------------------------------- /RichEditorViewSample/RichEditorViewSample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 54; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0CCBA517EA66E1DE61C19F69 /* Pods_RichEditorViewSample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1CA6B4DFCB1BCDED9BE91A70 /* Pods_RichEditorViewSample.framework */; }; 11 | 39883B491AD0DC270031FD16 /* KeyboardManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39883B481AD0DC270031FD16 /* KeyboardManager.swift */; }; 12 | 39BBCFB01AD0CC7A00A450D2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39BBCFAF1AD0CC7A00A450D2 /* AppDelegate.swift */; }; 13 | 39BBCFB21AD0CC7A00A450D2 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39BBCFB11AD0CC7A00A450D2 /* ViewController.swift */; }; 14 | 39BBCFB51AD0CC7A00A450D2 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 39BBCFB31AD0CC7A00A450D2 /* Main.storyboard */; }; 15 | 39BBCFB71AD0CC7A00A450D2 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 39BBCFB61AD0CC7A00A450D2 /* Images.xcassets */; }; 16 | 39BBCFBA1AD0CC7A00A450D2 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 39BBCFB81AD0CC7A00A450D2 /* LaunchScreen.xib */; }; 17 | 39BBCFC61AD0CC7A00A450D2 /* RichEditorViewSampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39BBCFC51AD0CC7A00A450D2 /* RichEditorViewSampleTests.swift */; }; 18 | 4886B7E92D6661E7006C6569 /* RichEditorView in Frameworks */ = {isa = PBXBuildFile; productRef = 4886B7E82D6661E7006C6569 /* RichEditorView */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXContainerItemProxy section */ 22 | 39BBCFC01AD0CC7A00A450D2 /* PBXContainerItemProxy */ = { 23 | isa = PBXContainerItemProxy; 24 | containerPortal = 39BBCFA21AD0CC7A00A450D2 /* Project object */; 25 | proxyType = 1; 26 | remoteGlobalIDString = 39BBCFA91AD0CC7A00A450D2; 27 | remoteInfo = RichEditorViewSample; 28 | }; 29 | /* End PBXContainerItemProxy section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | 1CA6B4DFCB1BCDED9BE91A70 /* Pods_RichEditorViewSample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RichEditorViewSample.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 33 | 3912A4531B966C34005E41FA /* RichEditorViewSample-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RichEditorViewSample-Bridging-Header.h"; sourceTree = " "; }; 34 | 39883B481AD0DC270031FD16 /* KeyboardManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardManager.swift; sourceTree = " "; }; 35 | 39BBCFAA1AD0CC7A00A450D2 /* RichEditorViewSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = RichEditorViewSample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 36 | 39BBCFAE1AD0CC7A00A450D2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = " "; }; 37 | 39BBCFAF1AD0CC7A00A450D2 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = " "; }; 38 | 39BBCFB11AD0CC7A00A450D2 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = " "; }; 39 | 39BBCFB41AD0CC7A00A450D2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = " "; }; 40 | 39BBCFB61AD0CC7A00A450D2 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = " "; }; 41 | 39BBCFB91AD0CC7A00A450D2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = " "; }; 42 | 39BBCFBF1AD0CC7A00A450D2 /* RichEditorViewSampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RichEditorViewSampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 43 | 39BBCFC41AD0CC7A00A450D2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = " "; }; 44 | 39BBCFC51AD0CC7A00A450D2 /* RichEditorViewSampleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RichEditorViewSampleTests.swift; sourceTree = " "; }; 45 | 39BBCFD01AD0CD4700A450D2 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = " "; }; 46 | 39BBCFD11AD0CD4700A450D2 /* RichEditorView.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = RichEditorView.podspec; path = ../RichEditorView.podspec; sourceTree = " "; xcLanguageSpecificationIdentifier = xcode.lang.ruby; }; 47 | 996D5C7A9F5BD7BF325118C8 /* Pods.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 48 | 9AF21FE45EA87DF1498534F6 /* Pods-RichEditorViewSample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RichEditorViewSample.release.xcconfig"; path = "Pods/Target Support Files/Pods-RichEditorViewSample/Pods-RichEditorViewSample.release.xcconfig"; sourceTree = " "; }; 49 | C6E2423BEEB12C12D184ACDD /* Pods-RichEditorViewSample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RichEditorViewSample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RichEditorViewSample/Pods-RichEditorViewSample.debug.xcconfig"; sourceTree = " "; }; 50 | /* End PBXFileReference section */ 51 | 52 | /* Begin PBXFrameworksBuildPhase section */ 53 | 39BBCFA71AD0CC7A00A450D2 /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | 4886B7E92D6661E7006C6569 /* RichEditorView in Frameworks */, 58 | 0CCBA517EA66E1DE61C19F69 /* Pods_RichEditorViewSample.framework in Frameworks */, 59 | ); 60 | runOnlyForDeploymentPostprocessing = 0; 61 | }; 62 | 39BBCFBC1AD0CC7A00A450D2 /* Frameworks */ = { 63 | isa = PBXFrameworksBuildPhase; 64 | buildActionMask = 2147483647; 65 | files = ( 66 | ); 67 | runOnlyForDeploymentPostprocessing = 0; 68 | }; 69 | /* End PBXFrameworksBuildPhase section */ 70 | 71 | /* Begin PBXGroup section */ 72 | 106F1EB9E1302E9C38A73C48 /* Frameworks */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | 996D5C7A9F5BD7BF325118C8 /* Pods.framework */, 76 | 1CA6B4DFCB1BCDED9BE91A70 /* Pods_RichEditorViewSample.framework */, 77 | ); 78 | name = Frameworks; 79 | sourceTree = " "; 80 | }; 81 | 39BBCFA11AD0CC7A00A450D2 = { 82 | isa = PBXGroup; 83 | children = ( 84 | 39BBCFCF1AD0CD3500A450D2 /* Pod Metadata */, 85 | 39BBCFAC1AD0CC7A00A450D2 /* RichEditorViewSample */, 86 | 39BBCFC21AD0CC7A00A450D2 /* RichEditorViewSampleTests */, 87 | 39BBCFAB1AD0CC7A00A450D2 /* Products */, 88 | 106F1EB9E1302E9C38A73C48 /* Frameworks */, 89 | 6B97E8B97DF9831B87D18089 /* Pods */, 90 | ); 91 | sourceTree = " "; 92 | }; 93 | 39BBCFAB1AD0CC7A00A450D2 /* Products */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | 39BBCFAA1AD0CC7A00A450D2 /* RichEditorViewSample.app */, 97 | 39BBCFBF1AD0CC7A00A450D2 /* RichEditorViewSampleTests.xctest */, 98 | ); 99 | name = Products; 100 | sourceTree = " "; 101 | }; 102 | 39BBCFAC1AD0CC7A00A450D2 /* RichEditorViewSample */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | 39BBCFAF1AD0CC7A00A450D2 /* AppDelegate.swift */, 106 | 39BBCFB11AD0CC7A00A450D2 /* ViewController.swift */, 107 | 39BBCFB31AD0CC7A00A450D2 /* Main.storyboard */, 108 | 39BBCFB61AD0CC7A00A450D2 /* Images.xcassets */, 109 | 39BBCFB81AD0CC7A00A450D2 /* LaunchScreen.xib */, 110 | 39BBCFAD1AD0CC7A00A450D2 /* Supporting Files */, 111 | 39883B481AD0DC270031FD16 /* KeyboardManager.swift */, 112 | 3912A4531B966C34005E41FA /* RichEditorViewSample-Bridging-Header.h */, 113 | ); 114 | path = RichEditorViewSample; 115 | sourceTree = " "; 116 | }; 117 | 39BBCFAD1AD0CC7A00A450D2 /* Supporting Files */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | 39BBCFAE1AD0CC7A00A450D2 /* Info.plist */, 121 | ); 122 | name = "Supporting Files"; 123 | sourceTree = " "; 124 | }; 125 | 39BBCFC21AD0CC7A00A450D2 /* RichEditorViewSampleTests */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | 39BBCFC51AD0CC7A00A450D2 /* RichEditorViewSampleTests.swift */, 129 | 39BBCFC31AD0CC7A00A450D2 /* Supporting Files */, 130 | ); 131 | path = RichEditorViewSampleTests; 132 | sourceTree = " "; 133 | }; 134 | 39BBCFC31AD0CC7A00A450D2 /* Supporting Files */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | 39BBCFC41AD0CC7A00A450D2 /* Info.plist */, 138 | ); 139 | name = "Supporting Files"; 140 | sourceTree = " "; 141 | }; 142 | 39BBCFCF1AD0CD3500A450D2 /* Pod Metadata */ = { 143 | isa = PBXGroup; 144 | children = ( 145 | 39BBCFD01AD0CD4700A450D2 /* README.md */, 146 | 39BBCFD11AD0CD4700A450D2 /* RichEditorView.podspec */, 147 | ); 148 | name = "Pod Metadata"; 149 | sourceTree = " "; 150 | }; 151 | 6B97E8B97DF9831B87D18089 /* Pods */ = { 152 | isa = PBXGroup; 153 | children = ( 154 | C6E2423BEEB12C12D184ACDD /* Pods-RichEditorViewSample.debug.xcconfig */, 155 | 9AF21FE45EA87DF1498534F6 /* Pods-RichEditorViewSample.release.xcconfig */, 156 | ); 157 | name = Pods; 158 | sourceTree = " "; 159 | }; 160 | /* End PBXGroup section */ 161 | 162 | /* Begin PBXNativeTarget section */ 163 | 39BBCFA91AD0CC7A00A450D2 /* RichEditorViewSample */ = { 164 | isa = PBXNativeTarget; 165 | buildConfigurationList = 39BBCFC91AD0CC7A00A450D2 /* Build configuration list for PBXNativeTarget "RichEditorViewSample" */; 166 | buildPhases = ( 167 | 4E74B098CF14DF0631DC2B86 /* [CP] Check Pods Manifest.lock */, 168 | 39BBCFA61AD0CC7A00A450D2 /* Sources */, 169 | 39BBCFA71AD0CC7A00A450D2 /* Frameworks */, 170 | 39BBCFA81AD0CC7A00A450D2 /* Resources */, 171 | ); 172 | buildRules = ( 173 | ); 174 | dependencies = ( 175 | ); 176 | name = RichEditorViewSample; 177 | packageProductDependencies = ( 178 | 4886B7E82D6661E7006C6569 /* RichEditorView */, 179 | ); 180 | productName = RichEditorViewSample; 181 | productReference = 39BBCFAA1AD0CC7A00A450D2 /* RichEditorViewSample.app */; 182 | productType = "com.apple.product-type.application"; 183 | }; 184 | 39BBCFBE1AD0CC7A00A450D2 /* RichEditorViewSampleTests */ = { 185 | isa = PBXNativeTarget; 186 | buildConfigurationList = 39BBCFCC1AD0CC7A00A450D2 /* Build configuration list for PBXNativeTarget "RichEditorViewSampleTests" */; 187 | buildPhases = ( 188 | 39BBCFBB1AD0CC7A00A450D2 /* Sources */, 189 | 39BBCFBC1AD0CC7A00A450D2 /* Frameworks */, 190 | 39BBCFBD1AD0CC7A00A450D2 /* Resources */, 191 | ); 192 | buildRules = ( 193 | ); 194 | dependencies = ( 195 | 39BBCFC11AD0CC7A00A450D2 /* PBXTargetDependency */, 196 | ); 197 | name = RichEditorViewSampleTests; 198 | productName = RichEditorViewSampleTests; 199 | productReference = 39BBCFBF1AD0CC7A00A450D2 /* RichEditorViewSampleTests.xctest */; 200 | productType = "com.apple.product-type.bundle.unit-test"; 201 | }; 202 | /* End PBXNativeTarget section */ 203 | 204 | /* Begin PBXProject section */ 205 | 39BBCFA21AD0CC7A00A450D2 /* Project object */ = { 206 | isa = PBXProject; 207 | attributes = { 208 | BuildIndependentTargetsInParallel = YES; 209 | LastSwiftUpdateCheck = 0700; 210 | LastUpgradeCheck = 1540; 211 | ORGANIZATIONNAME = "Caesar Wirth"; 212 | TargetAttributes = { 213 | 39BBCFA91AD0CC7A00A450D2 = { 214 | CreatedOnToolsVersion = 6.2; 215 | DevelopmentTeam = VZRAFDYUBF; 216 | LastSwiftMigration = 1100; 217 | }; 218 | 39BBCFBE1AD0CC7A00A450D2 = { 219 | CreatedOnToolsVersion = 6.2; 220 | LastSwiftMigration = 1100; 221 | TestTargetID = 39BBCFA91AD0CC7A00A450D2; 222 | }; 223 | }; 224 | }; 225 | buildConfigurationList = 39BBCFA51AD0CC7A00A450D2 /* Build configuration list for PBXProject "RichEditorViewSample" */; 226 | compatibilityVersion = "Xcode 3.2"; 227 | developmentRegion = en; 228 | hasScannedForEncodings = 0; 229 | knownRegions = ( 230 | en, 231 | Base, 232 | ); 233 | mainGroup = 39BBCFA11AD0CC7A00A450D2; 234 | packageReferences = ( 235 | 4886B7E72D6661B0006C6569 /* XCRemoteSwiftPackageReference "RichEditorView" */, 236 | ); 237 | productRefGroup = 39BBCFAB1AD0CC7A00A450D2 /* Products */; 238 | projectDirPath = ""; 239 | projectRoot = ""; 240 | targets = ( 241 | 39BBCFA91AD0CC7A00A450D2 /* RichEditorViewSample */, 242 | 39BBCFBE1AD0CC7A00A450D2 /* RichEditorViewSampleTests */, 243 | ); 244 | }; 245 | /* End PBXProject section */ 246 | 247 | /* Begin PBXResourcesBuildPhase section */ 248 | 39BBCFA81AD0CC7A00A450D2 /* Resources */ = { 249 | isa = PBXResourcesBuildPhase; 250 | buildActionMask = 2147483647; 251 | files = ( 252 | 39BBCFB51AD0CC7A00A450D2 /* Main.storyboard in Resources */, 253 | 39BBCFBA1AD0CC7A00A450D2 /* LaunchScreen.xib in Resources */, 254 | 39BBCFB71AD0CC7A00A450D2 /* Images.xcassets in Resources */, 255 | ); 256 | runOnlyForDeploymentPostprocessing = 0; 257 | }; 258 | 39BBCFBD1AD0CC7A00A450D2 /* Resources */ = { 259 | isa = PBXResourcesBuildPhase; 260 | buildActionMask = 2147483647; 261 | files = ( 262 | ); 263 | runOnlyForDeploymentPostprocessing = 0; 264 | }; 265 | /* End PBXResourcesBuildPhase section */ 266 | 267 | /* Begin PBXShellScriptBuildPhase section */ 268 | 4E74B098CF14DF0631DC2B86 /* [CP] Check Pods Manifest.lock */ = { 269 | isa = PBXShellScriptBuildPhase; 270 | buildActionMask = 2147483647; 271 | files = ( 272 | ); 273 | inputPaths = ( 274 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 275 | "${PODS_ROOT}/Manifest.lock", 276 | ); 277 | name = "[CP] Check Pods Manifest.lock"; 278 | outputPaths = ( 279 | "$(DERIVED_FILE_DIR)/Pods-RichEditorViewSample-checkManifestLockResult.txt", 280 | ); 281 | runOnlyForDeploymentPostprocessing = 0; 282 | shellPath = /bin/sh; 283 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 284 | showEnvVarsInLog = 0; 285 | }; 286 | /* End PBXShellScriptBuildPhase section */ 287 | 288 | /* Begin PBXSourcesBuildPhase section */ 289 | 39BBCFA61AD0CC7A00A450D2 /* Sources */ = { 290 | isa = PBXSourcesBuildPhase; 291 | buildActionMask = 2147483647; 292 | files = ( 293 | 39883B491AD0DC270031FD16 /* KeyboardManager.swift in Sources */, 294 | 39BBCFB21AD0CC7A00A450D2 /* ViewController.swift in Sources */, 295 | 39BBCFB01AD0CC7A00A450D2 /* AppDelegate.swift in Sources */, 296 | ); 297 | runOnlyForDeploymentPostprocessing = 0; 298 | }; 299 | 39BBCFBB1AD0CC7A00A450D2 /* Sources */ = { 300 | isa = PBXSourcesBuildPhase; 301 | buildActionMask = 2147483647; 302 | files = ( 303 | 39BBCFC61AD0CC7A00A450D2 /* RichEditorViewSampleTests.swift in Sources */, 304 | ); 305 | runOnlyForDeploymentPostprocessing = 0; 306 | }; 307 | /* End PBXSourcesBuildPhase section */ 308 | 309 | /* Begin PBXTargetDependency section */ 310 | 39BBCFC11AD0CC7A00A450D2 /* PBXTargetDependency */ = { 311 | isa = PBXTargetDependency; 312 | target = 39BBCFA91AD0CC7A00A450D2 /* RichEditorViewSample */; 313 | targetProxy = 39BBCFC01AD0CC7A00A450D2 /* PBXContainerItemProxy */; 314 | }; 315 | /* End PBXTargetDependency section */ 316 | 317 | /* Begin PBXVariantGroup section */ 318 | 39BBCFB31AD0CC7A00A450D2 /* Main.storyboard */ = { 319 | isa = PBXVariantGroup; 320 | children = ( 321 | 39BBCFB41AD0CC7A00A450D2 /* Base */, 322 | ); 323 | name = Main.storyboard; 324 | sourceTree = " "; 325 | }; 326 | 39BBCFB81AD0CC7A00A450D2 /* LaunchScreen.xib */ = { 327 | isa = PBXVariantGroup; 328 | children = ( 329 | 39BBCFB91AD0CC7A00A450D2 /* Base */, 330 | ); 331 | name = LaunchScreen.xib; 332 | sourceTree = " "; 333 | }; 334 | /* End PBXVariantGroup section */ 335 | 336 | /* Begin XCBuildConfiguration section */ 337 | 39BBCFC71AD0CC7A00A450D2 /* Debug */ = { 338 | isa = XCBuildConfiguration; 339 | buildSettings = { 340 | ALWAYS_SEARCH_USER_PATHS = NO; 341 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 342 | CLANG_CXX_LIBRARY = "libc++"; 343 | CLANG_ENABLE_MODULES = YES; 344 | CLANG_ENABLE_OBJC_ARC = YES; 345 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 346 | CLANG_WARN_BOOL_CONVERSION = YES; 347 | CLANG_WARN_COMMA = YES; 348 | CLANG_WARN_CONSTANT_CONVERSION = YES; 349 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 350 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 351 | CLANG_WARN_EMPTY_BODY = YES; 352 | CLANG_WARN_ENUM_CONVERSION = YES; 353 | CLANG_WARN_INFINITE_RECURSION = YES; 354 | CLANG_WARN_INT_CONVERSION = YES; 355 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 356 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 357 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 358 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 359 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 360 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 361 | CLANG_WARN_STRICT_PROTOTYPES = YES; 362 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 363 | CLANG_WARN_UNREACHABLE_CODE = YES; 364 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 365 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 366 | COPY_PHASE_STRIP = NO; 367 | ENABLE_STRICT_OBJC_MSGSEND = YES; 368 | ENABLE_TESTABILITY = YES; 369 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 370 | GCC_C_LANGUAGE_STANDARD = gnu99; 371 | GCC_DYNAMIC_NO_PIC = NO; 372 | GCC_NO_COMMON_BLOCKS = YES; 373 | GCC_OPTIMIZATION_LEVEL = 0; 374 | GCC_PREPROCESSOR_DEFINITIONS = ( 375 | "DEBUG=1", 376 | "$(inherited)", 377 | ); 378 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 379 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 380 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 381 | GCC_WARN_UNDECLARED_SELECTOR = YES; 382 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 383 | GCC_WARN_UNUSED_FUNCTION = YES; 384 | GCC_WARN_UNUSED_VARIABLE = YES; 385 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 386 | MTL_ENABLE_DEBUG_INFO = YES; 387 | ONLY_ACTIVE_ARCH = YES; 388 | SDKROOT = iphoneos; 389 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 390 | TARGETED_DEVICE_FAMILY = "1,2"; 391 | }; 392 | name = Debug; 393 | }; 394 | 39BBCFC81AD0CC7A00A450D2 /* Release */ = { 395 | isa = XCBuildConfiguration; 396 | buildSettings = { 397 | ALWAYS_SEARCH_USER_PATHS = NO; 398 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 399 | CLANG_CXX_LIBRARY = "libc++"; 400 | CLANG_ENABLE_MODULES = YES; 401 | CLANG_ENABLE_OBJC_ARC = YES; 402 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 403 | CLANG_WARN_BOOL_CONVERSION = YES; 404 | CLANG_WARN_COMMA = YES; 405 | CLANG_WARN_CONSTANT_CONVERSION = YES; 406 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 407 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 408 | CLANG_WARN_EMPTY_BODY = YES; 409 | CLANG_WARN_ENUM_CONVERSION = YES; 410 | CLANG_WARN_INFINITE_RECURSION = YES; 411 | CLANG_WARN_INT_CONVERSION = YES; 412 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 413 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 414 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 415 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 416 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 417 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 418 | CLANG_WARN_STRICT_PROTOTYPES = YES; 419 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 420 | CLANG_WARN_UNREACHABLE_CODE = YES; 421 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 422 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 423 | COPY_PHASE_STRIP = NO; 424 | ENABLE_NS_ASSERTIONS = NO; 425 | ENABLE_STRICT_OBJC_MSGSEND = YES; 426 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 427 | GCC_C_LANGUAGE_STANDARD = gnu99; 428 | GCC_NO_COMMON_BLOCKS = YES; 429 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 430 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 431 | GCC_WARN_UNDECLARED_SELECTOR = YES; 432 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 433 | GCC_WARN_UNUSED_FUNCTION = YES; 434 | GCC_WARN_UNUSED_VARIABLE = YES; 435 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 436 | MTL_ENABLE_DEBUG_INFO = NO; 437 | SDKROOT = iphoneos; 438 | SWIFT_COMPILATION_MODE = wholemodule; 439 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 440 | TARGETED_DEVICE_FAMILY = "1,2"; 441 | VALIDATE_PRODUCT = YES; 442 | }; 443 | name = Release; 444 | }; 445 | 39BBCFCA1AD0CC7A00A450D2 /* Debug */ = { 446 | isa = XCBuildConfiguration; 447 | baseConfigurationReference = C6E2423BEEB12C12D184ACDD /* Pods-RichEditorViewSample.debug.xcconfig */; 448 | buildSettings = { 449 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 450 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 451 | CLANG_ENABLE_MODULES = YES; 452 | DEVELOPMENT_TEAM = VZRAFDYUBF; 453 | INFOPLIST_FILE = RichEditorViewSample/Info.plist; 454 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 455 | PRODUCT_BUNDLE_IDENTIFIER = "com.cjwirth.$(PRODUCT_NAME:rfc1034identifier)"; 456 | PRODUCT_NAME = "$(TARGET_NAME)"; 457 | SWIFT_OBJC_BRIDGING_HEADER = "RichEditorViewSample/RichEditorViewSample-Bridging-Header.h"; 458 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 459 | SWIFT_VERSION = 4.2; 460 | }; 461 | name = Debug; 462 | }; 463 | 39BBCFCB1AD0CC7A00A450D2 /* Release */ = { 464 | isa = XCBuildConfiguration; 465 | baseConfigurationReference = 9AF21FE45EA87DF1498534F6 /* Pods-RichEditorViewSample.release.xcconfig */; 466 | buildSettings = { 467 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 468 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 469 | CLANG_ENABLE_MODULES = YES; 470 | DEVELOPMENT_TEAM = VZRAFDYUBF; 471 | INFOPLIST_FILE = RichEditorViewSample/Info.plist; 472 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 473 | PRODUCT_BUNDLE_IDENTIFIER = "com.cjwirth.$(PRODUCT_NAME:rfc1034identifier)"; 474 | PRODUCT_NAME = "$(TARGET_NAME)"; 475 | SWIFT_OBJC_BRIDGING_HEADER = "RichEditorViewSample/RichEditorViewSample-Bridging-Header.h"; 476 | SWIFT_VERSION = 4.2; 477 | }; 478 | name = Release; 479 | }; 480 | 39BBCFCD1AD0CC7A00A450D2 /* Debug */ = { 481 | isa = XCBuildConfiguration; 482 | buildSettings = { 483 | BUNDLE_LOADER = "$(TEST_HOST)"; 484 | GCC_PREPROCESSOR_DEFINITIONS = ( 485 | "DEBUG=1", 486 | "$(inherited)", 487 | ); 488 | INFOPLIST_FILE = RichEditorViewSampleTests/Info.plist; 489 | PRODUCT_BUNDLE_IDENTIFIER = "com.cjwirth.$(PRODUCT_NAME:rfc1034identifier)"; 490 | PRODUCT_NAME = "$(TARGET_NAME)"; 491 | SWIFT_VERSION = 4.2; 492 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RichEditorViewSample.app/RichEditorViewSample"; 493 | }; 494 | name = Debug; 495 | }; 496 | 39BBCFCE1AD0CC7A00A450D2 /* Release */ = { 497 | isa = XCBuildConfiguration; 498 | buildSettings = { 499 | BUNDLE_LOADER = "$(TEST_HOST)"; 500 | INFOPLIST_FILE = RichEditorViewSampleTests/Info.plist; 501 | PRODUCT_BUNDLE_IDENTIFIER = "com.cjwirth.$(PRODUCT_NAME:rfc1034identifier)"; 502 | PRODUCT_NAME = "$(TARGET_NAME)"; 503 | SWIFT_VERSION = 4.2; 504 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/RichEditorViewSample.app/RichEditorViewSample"; 505 | }; 506 | name = Release; 507 | }; 508 | /* End XCBuildConfiguration section */ 509 | 510 | /* Begin XCConfigurationList section */ 511 | 39BBCFA51AD0CC7A00A450D2 /* Build configuration list for PBXProject "RichEditorViewSample" */ = { 512 | isa = XCConfigurationList; 513 | buildConfigurations = ( 514 | 39BBCFC71AD0CC7A00A450D2 /* Debug */, 515 | 39BBCFC81AD0CC7A00A450D2 /* Release */, 516 | ); 517 | defaultConfigurationIsVisible = 0; 518 | defaultConfigurationName = Release; 519 | }; 520 | 39BBCFC91AD0CC7A00A450D2 /* Build configuration list for PBXNativeTarget "RichEditorViewSample" */ = { 521 | isa = XCConfigurationList; 522 | buildConfigurations = ( 523 | 39BBCFCA1AD0CC7A00A450D2 /* Debug */, 524 | 39BBCFCB1AD0CC7A00A450D2 /* Release */, 525 | ); 526 | defaultConfigurationIsVisible = 0; 527 | defaultConfigurationName = Release; 528 | }; 529 | 39BBCFCC1AD0CC7A00A450D2 /* Build configuration list for PBXNativeTarget "RichEditorViewSampleTests" */ = { 530 | isa = XCConfigurationList; 531 | buildConfigurations = ( 532 | 39BBCFCD1AD0CC7A00A450D2 /* Debug */, 533 | 39BBCFCE1AD0CC7A00A450D2 /* Release */, 534 | ); 535 | defaultConfigurationIsVisible = 0; 536 | defaultConfigurationName = Release; 537 | }; 538 | /* End XCConfigurationList section */ 539 | 540 | /* Begin XCRemoteSwiftPackageReference section */ 541 | 4886B7E72D6661B0006C6569 /* XCRemoteSwiftPackageReference "RichEditorView" */ = { 542 | isa = XCRemoteSwiftPackageReference; 543 | repositoryURL = "git@github.com:T-Pro/RichEditorView.git"; 544 | requirement = { 545 | branch = feature/swiftpm; 546 | kind = branch; 547 | }; 548 | }; 549 | /* End XCRemoteSwiftPackageReference section */ 550 | 551 | /* Begin XCSwiftPackageProductDependency section */ 552 | 4886B7E82D6661E7006C6569 /* RichEditorView */ = { 553 | isa = XCSwiftPackageProductDependency; 554 | package = 4886B7E72D6661B0006C6569 /* XCRemoteSwiftPackageReference "RichEditorView" */; 555 | productName = RichEditorView; 556 | }; 557 | /* End XCSwiftPackageProductDependency section */ 558 | }; 559 | rootObject = 39BBCFA21AD0CC7A00A450D2 /* Project object */; 560 | } 561 | -------------------------------------------------------------------------------- /RichEditorViewSample/RichEditorViewSample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | -------------------------------------------------------------------------------- /RichEditorViewSample/RichEditorViewSample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 |6 | 7 |4 | 9 | -------------------------------------------------------------------------------- /RichEditorViewSample/RichEditorViewSample.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 |5 | 8 |IDEDidComputeMac32BitWarning 6 |7 | 4 | 11 | -------------------------------------------------------------------------------- /RichEditorViewSample/RichEditorViewSample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 |6 | 7 |9 | 10 |4 | 9 | -------------------------------------------------------------------------------- /RichEditorViewSample/RichEditorViewSample.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "a1c2874af81cfd4b6bdb257fa3ae092f7f7ea6cfcbbb9490b79333bf50ca0ef5", 3 | "pins" : [ 4 | { 5 | "identity" : "richeditorview", 6 | "kind" : "remoteSourceControl", 7 | "location" : "git@github.com:T-Pro/RichEditorView.git", 8 | "state" : { 9 | "branch" : "feature/swiftpm", 10 | "revision" : "c8363bc4e827a2a559c1958f86f304f38a48b52a" 11 | } 12 | } 13 | ], 14 | "version" : 3 15 | } 16 | -------------------------------------------------------------------------------- /RichEditorViewSample/RichEditorViewSample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // RichEditorViewSample 4 | // 5 | // Created by Caesar Wirth on 4/5/15. 6 | // Copyright (c) 2015 Caesar Wirth. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /RichEditorViewSample/RichEditorViewSample/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 |5 | 8 |IDEDidComputeMac32BitWarning 6 |7 | 3 | 40 | -------------------------------------------------------------------------------- /RichEditorViewSample/RichEditorViewSample/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 |4 | 5 | 10 |6 | 7 | 8 | 9 | 11 | 34 |12 | 13 | 14 | 33 |15 | 16 | 17 | 23 | 24 |25 | 26 | 29 |27 | 28 | 30 | 31 | 32 | 35 | 39 |36 | 38 |37 | 3 | 76 | -------------------------------------------------------------------------------- /RichEditorViewSample/RichEditorViewSample/Images.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 | } -------------------------------------------------------------------------------- /RichEditorViewSample/RichEditorViewSample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 |4 | 5 | 9 |6 | 7 | 8 | 10 | 11 | 75 |12 | 26 | 27 |13 | 24 |14 | 22 |15 | 18 |16 | 17 | 19 | 21 |20 | 23 | 25 | 28 | 74 |29 | 72 |30 | 70 |31 | 34 |32 | 33 | 35 | 64 |36 | 37 | 38 | 52 |39 | 44 |40 | 41 | 43 |42 | 45 | 51 |46 | 47 | 48 | 49 | 50 | 53 | 54 | 63 |55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 65 | 66 | 69 |67 | 68 | 71 | 73 | 4 | 48 | -------------------------------------------------------------------------------- /RichEditorViewSample/RichEditorViewSample/KeyboardManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeyboardManager.swift 3 | // RichEditorViewSample 4 | // 5 | // Created by Caesar Wirth on 4/5/15. 6 | // Copyright (c) 2015 Caesar Wirth. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RichEditorView 11 | 12 | /** 13 | KeyboardManager is a class that takes care of showing and hiding the RichEditorToolbar when the keyboard is shown. 14 | As opposed to having this logic in multiple places, it is encapsulated in here. All that needs to change is the parent view. 15 | */ 16 | class KeyboardManager: NSObject { 17 | 18 | /** 19 | The parent view that the toolbar should be added to. 20 | Should normally be the top-level view of a UIViewController 21 | */ 22 | weak var view: UIView? 23 | 24 | /** 25 | The toolbar that will be shown and hidden. 26 | */ 27 | var toolbar: RichEditorToolbar 28 | 29 | init(view: UIView) { 30 | self.view = view 31 | toolbar = RichEditorToolbar(frame: CGRect(x: 0, y: view.bounds.height, width: view.bounds.width, height: 44)) 32 | // toolbar.options = RichEditorOptions.all() 33 | } 34 | 35 | /** 36 | Starts monitoring for keyboard notifications in order to show/hide the toolbar 37 | */ 38 | func beginMonitoring() { 39 | let sel = #selector(keyboardWillShowOrHide(_:)) 40 | NotificationCenter.default.addObserver(self, selector: sel, name: UIResponder.keyboardWillShowNotification, object: nil) 41 | NotificationCenter.default.addObserver(self, selector: sel, name: UIResponder.keyboardWillHideNotification, object: nil) 42 | } 43 | 44 | /** 45 | Stops monitoring for keyboard notifications 46 | */ 47 | func stopMonitoring() { 48 | NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillShowNotification, object: nil) 49 | NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardWillHideNotification, object: nil) 50 | } 51 | 52 | /** 53 | Called when a keyboard notification is recieved. Takes are of handling the showing or hiding of the toolbar 54 | */ 55 | @objc func keyboardWillShowOrHide(_ notification: Notification) { 56 | let info = notification.userInfo ?? [:] 57 | let duration = TimeInterval((info[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.floatValue ?? 0.25) 58 | let curve = UInt((info[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber)?.uintValue ?? 0) 59 | let options = UIView.AnimationOptions(rawValue: curve) 60 | let keyboardRect = (info[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue ?? CGRect.zero 61 | 62 | if notification.name == UIResponder.keyboardWillShowNotification { 63 | self.view?.addSubview(self.toolbar) 64 | UIView.animate(withDuration: duration, delay: 0, options: options, animations: { 65 | if let view = self.view { 66 | self.toolbar.frame.origin.y = view.frame.height - (keyboardRect.height + self.toolbar.frame.height) 67 | } 68 | }, completion: nil) 69 | 70 | 71 | } else if notification.name == UIResponder.keyboardWillHideNotification { 72 | UIView.animate(withDuration: duration, delay: 0, options: options, animations: { 73 | if let view = self.view { 74 | self.toolbar.frame.origin.y = view.frame.height 75 | } 76 | }, completion: nil) 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /RichEditorViewSample/RichEditorViewSample/RichEditorViewSample-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | -------------------------------------------------------------------------------- /RichEditorViewSample/RichEditorViewSample/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // RichEditorViewSample 4 | // 5 | // Created by Caesar Wirth on 4/5/15. 6 | // Copyright (c) 2015 Caesar Wirth. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RichEditorView 11 | 12 | class ViewController: UIViewController { 13 | 14 | @IBOutlet var editorView: RichEditorView! 15 | @IBOutlet var htmlTextView: UITextView! 16 | 17 | lazy var toolbar: RichEditorToolbar = { 18 | let toolbar = RichEditorToolbar(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width, height: 44)) 19 | toolbar.options = RichEditorDefaultOption.all 20 | return toolbar 21 | }() 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | 26 | editorView.delegate = self 27 | editorView.inputAccessoryView = toolbar 28 | editorView.placeholder = "Edit here" 29 | 30 | toolbar.delegate = self 31 | toolbar.editor = editorView 32 | editorView.html = "Jesus is God. He saves by grace through faith alone. Soli Deo gloria! perfectGod.com" 33 | 34 | // This will create a custom action that clears all the input text when it is pressed 35 | // let item = RichEditorOptionItem(image: nil, title: "Clear") { toolbar in 36 | // toolbar?.editor?.html = "" 37 | // } 38 | // 39 | // var options = toolbar.options 40 | // options.append(item) 41 | // toolbar.options = options 42 | } 43 | 44 | } 45 | 46 | extension ViewController: RichEditorDelegate { 47 | 48 | func richEditor(_ editor: RichEditorView, heightDidChange height: Int) { } 49 | 50 | func richEditor(_ editor: RichEditorView, contentDidChange content: String) { 51 | if content.isEmpty { 52 | htmlTextView.text = "HTML Preview" 53 | } else { 54 | htmlTextView.text = content 55 | } 56 | } 57 | 58 | func richEditorTookFocus(_ editor: RichEditorView) { } 59 | 60 | func richEditorLostFocus(_ editor: RichEditorView) { } 61 | 62 | func richEditorDidLoad(_ editor: RichEditorView) { } 63 | 64 | func richEditor(_ editor: RichEditorView, shouldInteractWith url: URL) -> Bool { return true } 65 | 66 | func richEditor(_ editor: RichEditorView, handleCustomAction content: String) { } 67 | 68 | } 69 | 70 | extension ViewController: RichEditorToolbarDelegate { 71 | 72 | fileprivate func randomColor() -> UIColor { 73 | let colors = [ 74 | UIColor.red, 75 | UIColor.orange, 76 | UIColor.yellow, 77 | UIColor.green, 78 | UIColor.blue, 79 | UIColor.purple 80 | ] 81 | 82 | let color = colors[Int(arc4random_uniform(UInt32(colors.count)))] 83 | return color 84 | } 85 | 86 | func richEditorToolbarChangeTextColor(_ toolbar: RichEditorToolbar) { 87 | let color = randomColor() 88 | toolbar.editor?.setTextColor(color) 89 | } 90 | 91 | func richEditorToolbarChangeBackgroundColor(_ toolbar: RichEditorToolbar) { 92 | let color = randomColor() 93 | toolbar.editor?.setTextBackgroundColor(color) 94 | } 95 | 96 | func richEditorToolbarInsertImage(_ toolbar: RichEditorToolbar) { 97 | toolbar.editor?.insertImage("https://gravatar.com/avatar/696cf5da599733261059de06c4d1fe22", alt: "Gravatar") 98 | } 99 | 100 | func richEditorToolbarInsertLink(_ toolbar: RichEditorToolbar) { 101 | // Can only add links to selected text, so make sure there is a range selection first 102 | // if let hasSelection = toolbar.editor?.rangeSelectionExists(), hasSelection { 103 | // toolbar.editor?.insertLink("http://github.com/cjwirth/RichEditorView", title: "Github Link") 104 | // } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /RichEditorViewSample/RichEditorViewSampleTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 |5 | 47 |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 |LSRequiresIPhoneOS 24 |25 | UILaunchStoryboardName 26 |LaunchScreen 27 |UIMainStoryboardFile 28 |Main 29 |UIRequiredDeviceCapabilities 30 |31 | 33 |armv7 32 |UISupportedInterfaceOrientations 34 |35 | 39 |UIInterfaceOrientationPortrait 36 |UIInterfaceOrientationLandscapeLeft 37 |UIInterfaceOrientationLandscapeRight 38 |UISupportedInterfaceOrientations~ipad 40 |41 | 46 |UIInterfaceOrientationPortrait 42 |UIInterfaceOrientationPortraitUpsideDown 43 |UIInterfaceOrientationLandscapeLeft 44 |UIInterfaceOrientationLandscapeRight 45 |4 | 25 | -------------------------------------------------------------------------------- /RichEditorViewSample/RichEditorViewSampleTests/RichEditorViewSampleTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RichEditorViewSampleTests.swift 3 | // RichEditorViewSampleTests 4 | // 5 | // Created by Caesar Wirth on 4/5/15. 6 | // Copyright (c) 2015 Caesar Wirth. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import XCTest 11 | 12 | class RichEditorViewSampleTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | XCTAssert(true, "Pass") 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure() { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /RichEditorViewTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 |5 | 24 |CFBundleDevelopmentRegion 6 |en 7 |CFBundleExecutable 8 |$(EXECUTABLE_NAME) 9 |CFBundleIdentifier 10 |$(PRODUCT_BUNDLE_IDENTIFIER) 11 |CFBundleInfoDictionaryVersion 12 |6.0 13 |CFBundleName 14 |$(PRODUCT_NAME) 15 |CFBundlePackageType 16 |BNDL 17 |CFBundleShortVersionString 18 |1.0 19 |CFBundleSignature 20 |???? 21 |CFBundleVersion 22 |1 23 |4 | 25 | -------------------------------------------------------------------------------- /RichEditorViewTests/RichEditorViewTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RichEditorViewTests.swift 3 | // RichEditorViewTests 4 | // 5 | // Created by Caesar Wirth on 4/7/15. 6 | // 7 | // 8 | 9 | import UIKit 10 | import XCTest 11 | 12 | class RichEditorViewTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | XCTAssert(true, "Pass") 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure() { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /art/Demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Pro/RichEditorView/0f90dfcfd2e6b8f63b8d6cdddeb70cc103dd2afd/art/Demo.gif -------------------------------------------------------------------------------- /art/Toolbar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/T-Pro/RichEditorView/0f90dfcfd2e6b8f63b8d6cdddeb70cc103dd2afd/art/Toolbar.gif --------------------------------------------------------------------------------5 | 24 |CFBundleDevelopmentRegion 6 |en 7 |CFBundleExecutable 8 |$(EXECUTABLE_NAME) 9 |CFBundleIdentifier 10 |$(PRODUCT_BUNDLE_IDENTIFIER) 11 |CFBundleInfoDictionaryVersion 12 |6.0 13 |CFBundleName 14 |$(PRODUCT_NAME) 15 |CFBundlePackageType 16 |BNDL 17 |CFBundleShortVersionString 18 |1.0 19 |CFBundleSignature 20 |???? 21 |CFBundleVersion 22 |1 23 |