├── .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 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Caesar Wirth 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 13 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "RichEditorView", 7 | platforms: [ 8 | .iOS(.v9) 9 | ], 10 | products: [ 11 | .library( 12 | name: "RichEditorView", 13 | targets: ["RichEditorView"] 14 | ), 15 | ], 16 | dependencies: [], 17 | targets: [ 18 | .target( 19 | name: "RichEditorView", 20 | dependencies: [], 21 | path: "RichEditorView/Sources", 22 | resources: [.process("Resources")] 23 | ), 24 | .testTarget( 25 | name: "RichEditorViewTests", 26 | dependencies: ["RichEditorView"], 27 | path: "RichEditorViewTests" 28 | ), 29 | ], 30 | swiftLanguageVersions: [.v5] 31 | ) 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | RichEditorView 2 | -------------- 3 | [![License: BSD 3](https://img.shields.io/badge/license-BSD3-blue.svg)](./LICENSE.md) 4 | [![Cocoapods](https://img.shields.io/cocoapods/v/RichEditorView.svg)](http://cocoapods.org/pods/RichEditorView) 5 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg)](https://github.com/Carthage/Carthage) 6 | 7 | Special thanks to `cjwirth` (Caesar Wirth) for creating this project. The impact of his work on T-Pro has been invaluable. 8 | We are committed to maintaining this library and welcome contributions through pull requests. 9 | 10 | RichEditorView is a simple, modular, drop-in UIView subclass for Rich Text Editing (`WKWebView` wrapper). 11 | 12 | Written in Swift 5.x (Xcode 11.x) 13 | 14 | Supports iOS 10 through SwiftPM, CocoaPods or Carthage. 15 | 16 | Seen in Action 17 | -------------- 18 | ![Demo](./art/Demo.gif) 19 | 20 | Just clone the project and open `RichEditorViewSample/RichEditorViewSample.xcworkspace` in Xcode. 21 | 22 | Features 23 | -------- 24 | 25 | ![Toolbar Demo](./art/Toolbar.gif) 26 | 27 | - [x] Bold 28 | - [x] Italic 29 | - [x] Subscript 30 | - [x] Superscript 31 | - [x] Strikethrough 32 | - [x] Underline 33 | - [x] Justify Left 34 | - [x] Justify Center 35 | - [x] Justify Right 36 | - [x] Heading 1 37 | - [x] Heading 2 38 | - [x] Heading 3 39 | - [x] Heading 4 40 | - [x] Heading 5 41 | - [x] Heading 6 42 | - [x] Undo 43 | - [x] Redo 44 | - [x] Ordered List 45 | - [x] Unordered List 46 | - [x] Indent 47 | - [x] Outdent 48 | - [x] Insert Image 49 | - [x] Insert Link 50 | - [x] Text Color 51 | - [x] Text Background Color 52 | 53 | Installation 54 | ------------ 55 | 56 | ### SwiftPM 57 | 58 | Add https://github.com/T-Pro/RichEditorView as a Swift Package Repository in Xcode and add `RichEditorView` as a Swift Package to your project. 59 | 60 | #### Cocoapods 61 | 62 | If you have Cocoapods 0.36+ installed, you can use Cocoapods to include `RichEditorView` into your project. 63 | Add the following to your `Podfile`: 64 | 65 | ``` 66 | pod "RichEditorView" 67 | ``` 68 | 69 | Note: the `use_frameworks!` is required for pods made in Swift. 70 | 71 | #### Carthage 72 | 73 | Add the following to your `Cartfile`: 74 | 75 | ``` 76 | github 'cjwirth/RichEditorView' 77 | ``` 78 | 79 | Using RichEditorView 80 | -------------------- 81 | 82 | `RichEditorView` makes no assumptions about how you want to use it in your app. It is a plain `UIView` subclass, so you are free to use it wherever, however you want. 83 | 84 | Most basic use: 85 | 86 | ``` 87 | editor = RichEditorView(frame: self.view.bounds) 88 | editor.html = "

My Awesome Editor

Now I am editing in style." 89 | self.view.addSubview(editor) 90 | ``` 91 | 92 | ### Editing Text 93 | 94 | To change the styles of the currently selected text, you just call methods directly on the `RichEditorView`: 95 | ```Swift 96 | editor.bold() 97 | editor.italic() 98 | editor.setTextColor(UIColor.red) 99 | ``` 100 | 101 | If you want to show the editing toolbar `RichEditorToolbar`, you will need to handle displaying it (`KeyboardManager.swift` in the sample project is a good start). But configuring it is as easy as telling it which options you want to enable, and telling it which `RichEditorView` to work on. 102 | 103 | ```Swift 104 | let toolbar = RichEditorToolbar(frame: CGRectMake(0, 0, 320, 44)) 105 | toolbar.options = RichEditorDefaultOption.all 106 | toolbar.editor = editor // Previously instantiated RichEditorView 107 | ``` 108 | 109 | Some actions require user feedback (such as select an image, choose a color, etc). In this cases you can conform to the `RichEditorToolbarDelegate` and react to these actions, and maybe display some custom UI. For example, from the sample project, we just select a random color: 110 | 111 | ```Swift 112 | private func randomColor() -> UIColor { 113 | let colors = [ 114 | UIColor.red, 115 | UIColor.orange, 116 | UIColor.yellow, 117 | UIColor.green, 118 | UIColor.blue, 119 | UIColor.purple 120 | ] 121 | 122 | let color = colors[Int(arc4random_uniform(UInt32(colors.count)))] 123 | return color 124 | } 125 | 126 | func richEditorToolbarChangeTextColor(toolbar: RichEditorToolbar) { 127 | let color = randomColor() 128 | toolbar.editor?.setTextColor(color) 129 | } 130 | ``` 131 | 132 | #### Advanced Editing 133 | 134 | If you need even more flexibility with your options, you can add completely custom actions, by either making an object that conforms the the `RichEditorOption` protocol, or configuring a `RichEditorOptionItem` object, and adding it to the toolbar's options: 135 | 136 | ```Swift 137 | let clearAllItem = RichEditorOptionItem(image: UIImage(named: "clear"), title: "Clear") { toolbar in 138 | toolbar?.editor?.html = "" 139 | return 140 | } 141 | toolbar.options = [clearAllItem] 142 | ``` 143 | 144 | 145 | Acknowledgements 146 | ---------------- 147 | 148 | * Modernized by: C. Bess 149 | * Caesar Wirth - cjwirth@gmail.com (original author) 150 | * [wasabeef/richeditor-android](https://github.com/wasabeef/richeditor-android) - Android version of this library (Apache v2) 151 | * [nnhubbard/ZSSRichTextEditor](https://github.com/nnhubbard/ZSSRichTextEditor) - Inspiration and Icons (MIT) 152 | 153 | License 154 | ------- 155 | 156 | RichEditorView is released under the BSD 3-Clause License. See [LICENSE.md](./LICENSE.md) for details. 157 | 158 | Soli Deo gloria 159 | -------------------------------------------------------------------------------- /RichEditorView.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "RichEditorView" 3 | s.version = "4.2.1" 4 | s.summary = "Rich Text Editor for iOS written in Swift" 5 | s.homepage = "https://github.com/T-Pro/RichEditorView" 6 | s.license = 'BSD 3-clause' 7 | s.author = { "Caesar Wirth" => "cjwirth@gmail.com", "Pedro Paulo de Amorim" => "pp.amorim@hotmail.com" } 8 | s.source = { :git => "https://github.com/T-Pro/RichEditorView.git", :tag => s.version.to_s } 9 | 10 | s.platform = :ios, '9.3' 11 | s.swift_version = '5.0' 12 | s.requires_arc = true 13 | 14 | s.source_files = 'RichEditorView/Sources/*' 15 | s.resources = [ 16 | 'RichEditorView/Sources/Resources/icons/*', 17 | 'RichEditorView/Sources/Resources/editor/*' 18 | ] 19 | end 20 | -------------------------------------------------------------------------------- /RichEditorView.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | FBA6AAFF1AD3EC9F00721644 /* RichEditorView.h in Headers */ = {isa = PBXBuildFile; fileRef = FBA6AAFE1AD3EC9F00721644 /* RichEditorView.h */; settings = {ATTRIBUTES = (Public, ); }; }; 11 | FBA6AB051AD3EC9F00721644 /* RichEditorView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FBA6AAF91AD3EC9F00721644 /* RichEditorView.framework */; }; 12 | FBA6AB0C1AD3EC9F00721644 /* RichEditorViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBA6AB0B1AD3EC9F00721644 /* RichEditorViewTests.swift */; }; 13 | FBA6AB8C1AD3ED3C00721644 /* Sources in Sources */ = {isa = PBXBuildFile; fileRef = FBA6AB871AD3ED3C00721644 /* Sources */; }; 14 | FBA6ABC51AD3FD2E00721644 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FBA6ABC41AD3FD2E00721644 /* UIKit.framework */; }; 15 | /* End PBXBuildFile section */ 16 | 17 | /* Begin PBXContainerItemProxy section */ 18 | FBA6AB061AD3EC9F00721644 /* PBXContainerItemProxy */ = { 19 | isa = PBXContainerItemProxy; 20 | containerPortal = FBA6AAF01AD3EC9F00721644 /* Project object */; 21 | proxyType = 1; 22 | remoteGlobalIDString = FBA6AAF81AD3EC9F00721644; 23 | remoteInfo = RichEditorView; 24 | }; 25 | /* End PBXContainerItemProxy section */ 26 | 27 | /* Begin PBXFileReference section */ 28 | FBA6AAF91AD3EC9F00721644 /* RichEditorView.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = RichEditorView.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 29 | FBA6AAFD1AD3EC9F00721644 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 30 | FBA6AAFE1AD3EC9F00721644 /* RichEditorView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RichEditorView.h; sourceTree = ""; }; 31 | FBA6AB041AD3EC9F00721644 /* RichEditorViewTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RichEditorViewTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 32 | FBA6AB0A1AD3EC9F00721644 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 33 | FBA6AB0B1AD3EC9F00721644 /* RichEditorViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RichEditorViewTests.swift; sourceTree = ""; }; 34 | FBA6AB871AD3ED3C00721644 /* Sources */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = folder; path = Sources; sourceTree = ""; }; 35 | FBA6AB951AD3F3E700721644 /* RichEditorView-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "RichEditorView-Bridging-Header.h"; sourceTree = ""; }; 36 | FBA6ABC41AD3FD2E00721644 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 37 | /* End PBXFileReference section */ 38 | 39 | /* Begin PBXFrameworksBuildPhase section */ 40 | FBA6AAF51AD3EC9F00721644 /* Frameworks */ = { 41 | isa = PBXFrameworksBuildPhase; 42 | buildActionMask = 2147483647; 43 | files = ( 44 | FBA6ABC51AD3FD2E00721644 /* UIKit.framework in Frameworks */, 45 | ); 46 | runOnlyForDeploymentPostprocessing = 0; 47 | }; 48 | FBA6AB011AD3EC9F00721644 /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | FBA6AB051AD3EC9F00721644 /* RichEditorView.framework in Frameworks */, 53 | ); 54 | runOnlyForDeploymentPostprocessing = 0; 55 | }; 56 | /* End PBXFrameworksBuildPhase section */ 57 | 58 | /* Begin PBXGroup section */ 59 | FBA6AAEF1AD3EC9F00721644 = { 60 | isa = PBXGroup; 61 | children = ( 62 | FBA6ABC41AD3FD2E00721644 /* UIKit.framework */, 63 | FBA6AAFB1AD3EC9F00721644 /* RichEditorView */, 64 | FBA6AB081AD3EC9F00721644 /* RichEditorViewTests */, 65 | FBA6AAFA1AD3EC9F00721644 /* Products */, 66 | ); 67 | sourceTree = ""; 68 | }; 69 | FBA6AAFA1AD3EC9F00721644 /* Products */ = { 70 | isa = PBXGroup; 71 | children = ( 72 | FBA6AAF91AD3EC9F00721644 /* RichEditorView.framework */, 73 | FBA6AB041AD3EC9F00721644 /* RichEditorViewTests.xctest */, 74 | ); 75 | name = Products; 76 | sourceTree = ""; 77 | }; 78 | FBA6AAFB1AD3EC9F00721644 /* RichEditorView */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | FBA6AB871AD3ED3C00721644 /* Sources */, 82 | FBA6AAFE1AD3EC9F00721644 /* RichEditorView.h */, 83 | FBA6AAFC1AD3EC9F00721644 /* Supporting Files */, 84 | ); 85 | path = RichEditorView; 86 | sourceTree = ""; 87 | }; 88 | FBA6AAFC1AD3EC9F00721644 /* Supporting Files */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | FBA6AAFD1AD3EC9F00721644 /* Info.plist */, 92 | FBA6AB951AD3F3E700721644 /* RichEditorView-Bridging-Header.h */, 93 | ); 94 | name = "Supporting Files"; 95 | sourceTree = ""; 96 | }; 97 | FBA6AB081AD3EC9F00721644 /* RichEditorViewTests */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | FBA6AB0B1AD3EC9F00721644 /* RichEditorViewTests.swift */, 101 | FBA6AB091AD3EC9F00721644 /* Supporting Files */, 102 | ); 103 | path = RichEditorViewTests; 104 | sourceTree = ""; 105 | }; 106 | FBA6AB091AD3EC9F00721644 /* Supporting Files */ = { 107 | isa = PBXGroup; 108 | children = ( 109 | FBA6AB0A1AD3EC9F00721644 /* Info.plist */, 110 | ); 111 | name = "Supporting Files"; 112 | sourceTree = ""; 113 | }; 114 | /* End PBXGroup section */ 115 | 116 | /* Begin PBXHeadersBuildPhase section */ 117 | FBA6AAF61AD3EC9F00721644 /* Headers */ = { 118 | isa = PBXHeadersBuildPhase; 119 | buildActionMask = 2147483647; 120 | files = ( 121 | FBA6AAFF1AD3EC9F00721644 /* RichEditorView.h in Headers */, 122 | ); 123 | runOnlyForDeploymentPostprocessing = 0; 124 | }; 125 | /* End PBXHeadersBuildPhase section */ 126 | 127 | /* Begin PBXNativeTarget section */ 128 | FBA6AAF81AD3EC9F00721644 /* RichEditorView */ = { 129 | isa = PBXNativeTarget; 130 | buildConfigurationList = FBA6AB0F1AD3EC9F00721644 /* Build configuration list for PBXNativeTarget "RichEditorView" */; 131 | buildPhases = ( 132 | FBA6AAF41AD3EC9F00721644 /* Sources */, 133 | FBA6AAF51AD3EC9F00721644 /* Frameworks */, 134 | FBA6AAF61AD3EC9F00721644 /* Headers */, 135 | FBA6AAF71AD3EC9F00721644 /* Resources */, 136 | ); 137 | buildRules = ( 138 | ); 139 | dependencies = ( 140 | ); 141 | name = RichEditorView; 142 | productName = RichEditorView; 143 | productReference = FBA6AAF91AD3EC9F00721644 /* RichEditorView.framework */; 144 | productType = "com.apple.product-type.framework"; 145 | }; 146 | FBA6AB031AD3EC9F00721644 /* RichEditorViewTests */ = { 147 | isa = PBXNativeTarget; 148 | buildConfigurationList = FBA6AB121AD3EC9F00721644 /* Build configuration list for PBXNativeTarget "RichEditorViewTests" */; 149 | buildPhases = ( 150 | FBA6AB001AD3EC9F00721644 /* Sources */, 151 | FBA6AB011AD3EC9F00721644 /* Frameworks */, 152 | FBA6AB021AD3EC9F00721644 /* Resources */, 153 | ); 154 | buildRules = ( 155 | ); 156 | dependencies = ( 157 | FBA6AB071AD3EC9F00721644 /* PBXTargetDependency */, 158 | ); 159 | name = RichEditorViewTests; 160 | productName = RichEditorViewTests; 161 | productReference = FBA6AB041AD3EC9F00721644 /* RichEditorViewTests.xctest */; 162 | productType = "com.apple.product-type.bundle.unit-test"; 163 | }; 164 | /* End PBXNativeTarget section */ 165 | 166 | /* Begin PBXProject section */ 167 | FBA6AAF01AD3EC9F00721644 /* Project object */ = { 168 | isa = PBXProject; 169 | attributes = { 170 | LastSwiftUpdateCheck = 0710; 171 | LastUpgradeCheck = 0820; 172 | TargetAttributes = { 173 | FBA6AAF81AD3EC9F00721644 = { 174 | CreatedOnToolsVersion = 6.2; 175 | LastSwiftMigration = 0820; 176 | }; 177 | FBA6AB031AD3EC9F00721644 = { 178 | CreatedOnToolsVersion = 6.2; 179 | DevelopmentTeam = VZRAFDYUBF; 180 | LastSwiftMigration = 0820; 181 | }; 182 | }; 183 | }; 184 | buildConfigurationList = FBA6AAF31AD3EC9F00721644 /* Build configuration list for PBXProject "RichEditorView" */; 185 | compatibilityVersion = "Xcode 3.2"; 186 | developmentRegion = English; 187 | hasScannedForEncodings = 0; 188 | knownRegions = ( 189 | English, 190 | en, 191 | ); 192 | mainGroup = FBA6AAEF1AD3EC9F00721644; 193 | productRefGroup = FBA6AAFA1AD3EC9F00721644 /* Products */; 194 | projectDirPath = ""; 195 | projectRoot = ""; 196 | targets = ( 197 | FBA6AAF81AD3EC9F00721644 /* RichEditorView */, 198 | FBA6AB031AD3EC9F00721644 /* RichEditorViewTests */, 199 | ); 200 | }; 201 | /* End PBXProject section */ 202 | 203 | /* Begin PBXResourcesBuildPhase section */ 204 | FBA6AAF71AD3EC9F00721644 /* Resources */ = { 205 | isa = PBXResourcesBuildPhase; 206 | buildActionMask = 2147483647; 207 | files = ( 208 | ); 209 | runOnlyForDeploymentPostprocessing = 0; 210 | }; 211 | FBA6AB021AD3EC9F00721644 /* Resources */ = { 212 | isa = PBXResourcesBuildPhase; 213 | buildActionMask = 2147483647; 214 | files = ( 215 | ); 216 | runOnlyForDeploymentPostprocessing = 0; 217 | }; 218 | /* End PBXResourcesBuildPhase section */ 219 | 220 | /* Begin PBXSourcesBuildPhase section */ 221 | FBA6AAF41AD3EC9F00721644 /* Sources */ = { 222 | isa = PBXSourcesBuildPhase; 223 | buildActionMask = 2147483647; 224 | files = ( 225 | FBA6AB8C1AD3ED3C00721644 /* Sources in Sources */, 226 | ); 227 | runOnlyForDeploymentPostprocessing = 0; 228 | }; 229 | FBA6AB001AD3EC9F00721644 /* Sources */ = { 230 | isa = PBXSourcesBuildPhase; 231 | buildActionMask = 2147483647; 232 | files = ( 233 | FBA6AB0C1AD3EC9F00721644 /* RichEditorViewTests.swift in Sources */, 234 | ); 235 | runOnlyForDeploymentPostprocessing = 0; 236 | }; 237 | /* End PBXSourcesBuildPhase section */ 238 | 239 | /* Begin PBXTargetDependency section */ 240 | FBA6AB071AD3EC9F00721644 /* PBXTargetDependency */ = { 241 | isa = PBXTargetDependency; 242 | target = FBA6AAF81AD3EC9F00721644 /* RichEditorView */; 243 | targetProxy = FBA6AB061AD3EC9F00721644 /* PBXContainerItemProxy */; 244 | }; 245 | /* End PBXTargetDependency section */ 246 | 247 | /* Begin XCBuildConfiguration section */ 248 | FBA6AB0D1AD3EC9F00721644 /* Debug */ = { 249 | isa = XCBuildConfiguration; 250 | buildSettings = { 251 | ALWAYS_SEARCH_USER_PATHS = NO; 252 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 253 | CLANG_CXX_LIBRARY = "libc++"; 254 | CLANG_ENABLE_MODULES = YES; 255 | CLANG_ENABLE_OBJC_ARC = YES; 256 | CLANG_WARN_BOOL_CONVERSION = YES; 257 | CLANG_WARN_CONSTANT_CONVERSION = YES; 258 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 259 | CLANG_WARN_EMPTY_BODY = YES; 260 | CLANG_WARN_ENUM_CONVERSION = YES; 261 | CLANG_WARN_INFINITE_RECURSION = YES; 262 | CLANG_WARN_INT_CONVERSION = YES; 263 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 264 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 265 | CLANG_WARN_UNREACHABLE_CODE = YES; 266 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 267 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 268 | COPY_PHASE_STRIP = NO; 269 | CURRENT_PROJECT_VERSION = 1; 270 | ENABLE_STRICT_OBJC_MSGSEND = YES; 271 | ENABLE_TESTABILITY = YES; 272 | GCC_C_LANGUAGE_STANDARD = gnu99; 273 | GCC_DYNAMIC_NO_PIC = NO; 274 | GCC_NO_COMMON_BLOCKS = YES; 275 | GCC_OPTIMIZATION_LEVEL = 0; 276 | GCC_PREPROCESSOR_DEFINITIONS = ( 277 | "DEBUG=1", 278 | "$(inherited)", 279 | ); 280 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 281 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 282 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 283 | GCC_WARN_UNDECLARED_SELECTOR = YES; 284 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 285 | GCC_WARN_UNUSED_FUNCTION = YES; 286 | GCC_WARN_UNUSED_VARIABLE = YES; 287 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 288 | MTL_ENABLE_DEBUG_INFO = YES; 289 | ONLY_ACTIVE_ARCH = YES; 290 | SDKROOT = iphoneos; 291 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 292 | SWIFT_VERSION = 5.0; 293 | TARGETED_DEVICE_FAMILY = "1,2"; 294 | VERSIONING_SYSTEM = "apple-generic"; 295 | VERSION_INFO_PREFIX = ""; 296 | }; 297 | name = Debug; 298 | }; 299 | FBA6AB0E1AD3EC9F00721644 /* Release */ = { 300 | isa = XCBuildConfiguration; 301 | buildSettings = { 302 | ALWAYS_SEARCH_USER_PATHS = NO; 303 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 304 | CLANG_CXX_LIBRARY = "libc++"; 305 | CLANG_ENABLE_MODULES = YES; 306 | CLANG_ENABLE_OBJC_ARC = YES; 307 | CLANG_WARN_BOOL_CONVERSION = YES; 308 | CLANG_WARN_CONSTANT_CONVERSION = YES; 309 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 310 | CLANG_WARN_EMPTY_BODY = YES; 311 | CLANG_WARN_ENUM_CONVERSION = YES; 312 | CLANG_WARN_INFINITE_RECURSION = YES; 313 | CLANG_WARN_INT_CONVERSION = YES; 314 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 315 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 316 | CLANG_WARN_UNREACHABLE_CODE = YES; 317 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 318 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 319 | COPY_PHASE_STRIP = YES; 320 | CURRENT_PROJECT_VERSION = 1; 321 | ENABLE_NS_ASSERTIONS = NO; 322 | ENABLE_STRICT_OBJC_MSGSEND = YES; 323 | GCC_C_LANGUAGE_STANDARD = gnu99; 324 | GCC_NO_COMMON_BLOCKS = YES; 325 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 326 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 327 | GCC_WARN_UNDECLARED_SELECTOR = YES; 328 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 329 | GCC_WARN_UNUSED_FUNCTION = YES; 330 | GCC_WARN_UNUSED_VARIABLE = YES; 331 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 332 | MTL_ENABLE_DEBUG_INFO = NO; 333 | SDKROOT = iphoneos; 334 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 335 | SWIFT_VERSION = 5.0; 336 | TARGETED_DEVICE_FAMILY = "1,2"; 337 | VALIDATE_PRODUCT = YES; 338 | VERSIONING_SYSTEM = "apple-generic"; 339 | VERSION_INFO_PREFIX = ""; 340 | }; 341 | name = Release; 342 | }; 343 | FBA6AB101AD3EC9F00721644 /* Debug */ = { 344 | isa = XCBuildConfiguration; 345 | buildSettings = { 346 | CLANG_ENABLE_MODULES = YES; 347 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 348 | DEFINES_MODULE = YES; 349 | DYLIB_COMPATIBILITY_VERSION = 1; 350 | DYLIB_CURRENT_VERSION = 1; 351 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 352 | INFOPLIST_FILE = RichEditorView/Info.plist; 353 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 354 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 355 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 356 | PRODUCT_BUNDLE_IDENTIFIER = "com.cjwirth.$(PRODUCT_NAME:rfc1034identifier)"; 357 | PRODUCT_NAME = "$(TARGET_NAME)"; 358 | SKIP_INSTALL = YES; 359 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 360 | SWIFT_VERSION = 5.0; 361 | }; 362 | name = Debug; 363 | }; 364 | FBA6AB111AD3EC9F00721644 /* Release */ = { 365 | isa = XCBuildConfiguration; 366 | buildSettings = { 367 | CLANG_ENABLE_MODULES = YES; 368 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 369 | DEFINES_MODULE = YES; 370 | DYLIB_COMPATIBILITY_VERSION = 1; 371 | DYLIB_CURRENT_VERSION = 1; 372 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 373 | INFOPLIST_FILE = RichEditorView/Info.plist; 374 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 375 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 376 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 377 | PRODUCT_BUNDLE_IDENTIFIER = "com.cjwirth.$(PRODUCT_NAME:rfc1034identifier)"; 378 | PRODUCT_NAME = "$(TARGET_NAME)"; 379 | SKIP_INSTALL = YES; 380 | SWIFT_VERSION = 5.0; 381 | }; 382 | name = Release; 383 | }; 384 | FBA6AB131AD3EC9F00721644 /* Debug */ = { 385 | isa = XCBuildConfiguration; 386 | buildSettings = { 387 | DEVELOPMENT_TEAM = VZRAFDYUBF; 388 | FRAMEWORK_SEARCH_PATHS = ( 389 | "$(SDKROOT)/Developer/Library/Frameworks", 390 | "$(inherited)", 391 | ); 392 | GCC_PREPROCESSOR_DEFINITIONS = ( 393 | "DEBUG=1", 394 | "$(inherited)", 395 | ); 396 | INFOPLIST_FILE = RichEditorViewTests/Info.plist; 397 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 398 | PRODUCT_BUNDLE_IDENTIFIER = "com.cjwirth.$(PRODUCT_NAME:rfc1034identifier)"; 399 | PRODUCT_NAME = "$(TARGET_NAME)"; 400 | SWIFT_VERSION = 5.0; 401 | }; 402 | name = Debug; 403 | }; 404 | FBA6AB141AD3EC9F00721644 /* Release */ = { 405 | isa = XCBuildConfiguration; 406 | buildSettings = { 407 | DEVELOPMENT_TEAM = VZRAFDYUBF; 408 | FRAMEWORK_SEARCH_PATHS = ( 409 | "$(SDKROOT)/Developer/Library/Frameworks", 410 | "$(inherited)", 411 | ); 412 | INFOPLIST_FILE = RichEditorViewTests/Info.plist; 413 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 414 | PRODUCT_BUNDLE_IDENTIFIER = "com.cjwirth.$(PRODUCT_NAME:rfc1034identifier)"; 415 | PRODUCT_NAME = "$(TARGET_NAME)"; 416 | SWIFT_VERSION = 5.0; 417 | }; 418 | name = Release; 419 | }; 420 | /* End XCBuildConfiguration section */ 421 | 422 | /* Begin XCConfigurationList section */ 423 | FBA6AAF31AD3EC9F00721644 /* Build configuration list for PBXProject "RichEditorView" */ = { 424 | isa = XCConfigurationList; 425 | buildConfigurations = ( 426 | FBA6AB0D1AD3EC9F00721644 /* Debug */, 427 | FBA6AB0E1AD3EC9F00721644 /* Release */, 428 | ); 429 | defaultConfigurationIsVisible = 0; 430 | defaultConfigurationName = Release; 431 | }; 432 | FBA6AB0F1AD3EC9F00721644 /* Build configuration list for PBXNativeTarget "RichEditorView" */ = { 433 | isa = XCConfigurationList; 434 | buildConfigurations = ( 435 | FBA6AB101AD3EC9F00721644 /* Debug */, 436 | FBA6AB111AD3EC9F00721644 /* Release */, 437 | ); 438 | defaultConfigurationIsVisible = 0; 439 | defaultConfigurationName = Release; 440 | }; 441 | FBA6AB121AD3EC9F00721644 /* Build configuration list for PBXNativeTarget "RichEditorViewTests" */ = { 442 | isa = XCConfigurationList; 443 | buildConfigurations = ( 444 | FBA6AB131AD3EC9F00721644 /* Debug */, 445 | FBA6AB141AD3EC9F00721644 /* Release */, 446 | ); 447 | defaultConfigurationIsVisible = 0; 448 | defaultConfigurationName = Release; 449 | }; 450 | /* End XCConfigurationList section */ 451 | }; 452 | rootObject = FBA6AAF01AD3EC9F00721644 /* Project object */; 453 | } 454 | -------------------------------------------------------------------------------- /RichEditorView.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /RichEditorView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /RichEditorView.xcodeproj/xcshareddata/xcschemes/RichEditorView.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 79 | 85 | 86 | 87 | 88 | 89 | 90 | 96 | 97 | 103 | 104 | 105 | 106 | 108 | 109 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /RichEditorView/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /RichEditorView/RichEditorView-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // RichEditorView-Bridging-Header.h 3 | // RichEditorView 4 | // 5 | // Created by Caesar Wirth on 4/7/15. 6 | // 7 | // 8 | 9 | #ifndef RichEditorView_RichEditorView_Bridging_Header_h 10 | #define RichEditorView_RichEditorView_Bridging_Header_h 11 | 12 | #import "CJWWebView+HackishAccessoryHiding.h" 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /RichEditorView/RichEditorView.h: -------------------------------------------------------------------------------- 1 | // 2 | // RichEditorView.h 3 | // RichEditorView 4 | // 5 | // Created by Caesar Wirth on 4/7/15. 6 | // 7 | // 8 | 9 | #import 10 | 11 | #import 12 | 13 | //! Project version number for RichEditorView. 14 | FOUNDATION_EXPORT double RichEditorViewVersionNumber; 15 | 16 | //! Project version string for RichEditorView. 17 | FOUNDATION_EXPORT const unsigned char RichEditorViewVersionString[]; 18 | 19 | // In this header, you should import all the public headers of your framework using statements like #import 20 | 21 | 22 | -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/editor/assert.js: -------------------------------------------------------------------------------- 1 | //worlds smallest unit testing framework 2 | var Assert = (Assert = function() { 3 | var AssertException = function(message) { 4 | this.message = message; 5 | }; 6 | 7 | AssertException.prototype.toString = function() { 8 | return 'AssertException: ' + this.message; 9 | }; 10 | 11 | this.assert = this.that = function(exp, message) { 12 | 13 | if (!exp) { 14 | throw new AssertException(message); 15 | } 16 | } 17 | 18 | this.equals = function(expected, actual, detail) { 19 | this.assert(expected == actual, 'Failed asserting that ' + expected + '===' + actual + ' :: ' + detail); 20 | } 21 | 22 | this.type = function(expectedType, actual) { 23 | this.assert(expectedType === actual.constructor.name, 'Failed asserting that types match: ' + expectedType + ' === ' + actual.constructor.name); 24 | } 25 | //end worlds smallest unit testing framework 26 | return this; 27 | }()); -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/editor/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v8.0.0 | MIT License | github.com/necolas/normalize.css */ 2 | 3 | /* Document 4 | ========================================================================== */ 5 | 6 | /** 7 | * 1. Correct the line height in all browsers. 8 | * 2. Prevent adjustments of font size after orientation changes in iOS. 9 | */ 10 | 11 | html { 12 | line-height: 1.15; /* 1 */ 13 | -webkit-text-size-adjust: 100%; /* 2 */ 14 | } 15 | 16 | /* Sections 17 | ========================================================================== */ 18 | 19 | /** 20 | * Remove the margin in all browsers. 21 | */ 22 | 23 | body { 24 | margin: 0; 25 | } 26 | 27 | /** 28 | * Correct the font size and margin on `h1` elements within `section` and 29 | * `article` contexts in Chrome, Firefox, and Safari. 30 | */ 31 | 32 | h1 { 33 | font-size: 2em; 34 | margin: 0.67em 0; 35 | } 36 | 37 | /* Grouping content 38 | ========================================================================== */ 39 | 40 | /** 41 | * 1. Add the correct box sizing in Firefox. 42 | * 2. Show the overflow in Edge and IE. 43 | */ 44 | 45 | hr { 46 | box-sizing: content-box; /* 1 */ 47 | height: 0; /* 1 */ 48 | overflow: visible; /* 2 */ 49 | } 50 | 51 | /** 52 | * 1. Correct the inheritance and scaling of font size in all browsers. 53 | * 2. Correct the odd `em` font sizing in all browsers. 54 | */ 55 | 56 | pre { 57 | font-family: monospace, monospace; /* 1 */ 58 | font-size: 1em; /* 2 */ 59 | } 60 | 61 | /* Text-level semantics 62 | ========================================================================== */ 63 | 64 | /** 65 | * Remove the gray background on active links in IE 10. 66 | */ 67 | 68 | a { 69 | background-color: transparent; 70 | } 71 | 72 | /** 73 | * 1. Remove the bottom border in Chrome 57- 74 | * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. 75 | */ 76 | 77 | abbr[title] { 78 | border-bottom: none; /* 1 */ 79 | text-decoration: underline; /* 2 */ 80 | text-decoration: underline dotted; /* 2 */ 81 | } 82 | 83 | /** 84 | * Add the correct font weight in Chrome, Edge, and Safari. 85 | */ 86 | 87 | b, 88 | strong { 89 | font-weight: bolder; 90 | } 91 | 92 | /** 93 | * 1. Correct the inheritance and scaling of font size in all browsers. 94 | * 2. Correct the odd `em` font sizing in all browsers. 95 | */ 96 | 97 | code, 98 | kbd, 99 | samp { 100 | font-family: monospace, monospace; /* 1 */ 101 | font-size: 1em; /* 2 */ 102 | } 103 | 104 | /** 105 | * Add the correct font size in all browsers. 106 | */ 107 | 108 | small { 109 | font-size: 80%; 110 | } 111 | 112 | /** 113 | * Prevent `sub` and `sup` elements from affecting the line height in 114 | * all browsers. 115 | */ 116 | 117 | sub, 118 | sup { 119 | font-size: 75%; 120 | line-height: 0; 121 | position: relative; 122 | vertical-align: baseline; 123 | } 124 | 125 | sub { 126 | bottom: -0.25em; 127 | } 128 | 129 | sup { 130 | top: -0.5em; 131 | } 132 | 133 | /* Embedded content 134 | ========================================================================== */ 135 | 136 | /** 137 | * Remove the border on images inside links in IE 10. 138 | */ 139 | 140 | img { 141 | border-style: none; 142 | } 143 | 144 | /* Forms 145 | ========================================================================== */ 146 | 147 | /** 148 | * 1. Change the font styles in all browsers. 149 | * 2. Remove the margin in Firefox and Safari. 150 | */ 151 | 152 | button, 153 | input, 154 | optgroup, 155 | select, 156 | textarea { 157 | font-family: inherit; /* 1 */ 158 | font-size: 100%; /* 1 */ 159 | line-height: 1.15; /* 1 */ 160 | margin: 0; /* 2 */ 161 | } 162 | 163 | /** 164 | * Show the overflow in IE. 165 | * 1. Show the overflow in Edge. 166 | */ 167 | 168 | button, 169 | input { /* 1 */ 170 | overflow: visible; 171 | } 172 | 173 | /** 174 | * Remove the inheritance of text transform in Edge, Firefox, and IE. 175 | * 1. Remove the inheritance of text transform in Firefox. 176 | */ 177 | 178 | button, 179 | select { /* 1 */ 180 | text-transform: none; 181 | } 182 | 183 | /** 184 | * Correct the inability to style clickable types in iOS and Safari. 185 | */ 186 | 187 | button, 188 | [type="button"], 189 | [type="reset"], 190 | [type="submit"] { 191 | -webkit-appearance: button; 192 | } 193 | 194 | /** 195 | * Remove the inner border and padding in Firefox. 196 | */ 197 | 198 | button::-moz-focus-inner, 199 | [type="button"]::-moz-focus-inner, 200 | [type="reset"]::-moz-focus-inner, 201 | [type="submit"]::-moz-focus-inner { 202 | border-style: none; 203 | padding: 0; 204 | } 205 | 206 | /** 207 | * Restore the focus styles unset by the previous rule. 208 | */ 209 | 210 | button:-moz-focusring, 211 | [type="button"]:-moz-focusring, 212 | [type="reset"]:-moz-focusring, 213 | [type="submit"]:-moz-focusring { 214 | outline: 1px dotted ButtonText; 215 | } 216 | 217 | /** 218 | * Correct the padding in Firefox. 219 | */ 220 | 221 | fieldset { 222 | padding: 0.35em 0.75em 0.625em; 223 | } 224 | 225 | /** 226 | * 1. Correct the text wrapping in Edge and IE. 227 | * 2. Correct the color inheritance from `fieldset` elements in IE. 228 | * 3. Remove the padding so developers are not caught out when they zero out 229 | * `fieldset` elements in all browsers. 230 | */ 231 | 232 | legend { 233 | box-sizing: border-box; /* 1 */ 234 | color: inherit; /* 2 */ 235 | display: table; /* 1 */ 236 | max-width: 100%; /* 1 */ 237 | padding: 0; /* 3 */ 238 | white-space: normal; /* 1 */ 239 | } 240 | 241 | /** 242 | * Add the correct vertical alignment in Chrome, Firefox, and Opera. 243 | */ 244 | 245 | progress { 246 | vertical-align: baseline; 247 | } 248 | 249 | /** 250 | * Remove the default vertical scrollbar in IE 10+. 251 | */ 252 | 253 | textarea { 254 | overflow: auto; 255 | } 256 | 257 | /** 258 | * 1. Add the correct box sizing in IE 10. 259 | * 2. Remove the padding in IE 10. 260 | */ 261 | 262 | [type="checkbox"], 263 | [type="radio"] { 264 | box-sizing: border-box; /* 1 */ 265 | padding: 0; /* 2 */ 266 | } 267 | 268 | /** 269 | * Correct the cursor style of increment and decrement buttons in Chrome. 270 | */ 271 | 272 | [type="number"]::-webkit-inner-spin-button, 273 | [type="number"]::-webkit-outer-spin-button { 274 | height: auto; 275 | } 276 | 277 | /** 278 | * 1. Correct the odd appearance in Chrome and Safari. 279 | * 2. Correct the outline style in Safari. 280 | */ 281 | 282 | [type="search"] { 283 | -webkit-appearance: textfield; /* 1 */ 284 | outline-offset: -2px; /* 2 */ 285 | } 286 | 287 | /** 288 | * Remove the inner padding in Chrome and Safari on macOS. 289 | */ 290 | 291 | [type="search"]::-webkit-search-decoration { 292 | -webkit-appearance: none; 293 | } 294 | 295 | /** 296 | * 1. Correct the inability to style clickable types in iOS and Safari. 297 | * 2. Change font properties to `inherit` in Safari. 298 | */ 299 | 300 | ::-webkit-file-upload-button { 301 | -webkit-appearance: button; /* 1 */ 302 | font: inherit; /* 2 */ 303 | } 304 | 305 | /* Interactive 306 | ========================================================================== */ 307 | 308 | /* 309 | * Add the correct display in Edge, IE 10+, and Firefox. 310 | */ 311 | 312 | details { 313 | display: block; 314 | } 315 | 316 | /* 317 | * Add the correct display in all browsers. 318 | */ 319 | 320 | summary { 321 | display: list-item; 322 | } 323 | 324 | /* Misc 325 | ========================================================================== */ 326 | 327 | /** 328 | * Add the correct display in IE 10+. 329 | */ 330 | 331 | template { 332 | display: none; 333 | } 334 | 335 | /** 336 | * Add the correct display in IE 10. 337 | */ 338 | 339 | [hidden] { 340 | display: none; 341 | } 342 | -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/editor/rich_editor.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /RichEditorView/Sources/Resources/editor/rich_editor.js: -------------------------------------------------------------------------------- 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 | "use strict"; 17 | 18 | const RE = {}; 19 | 20 | RE.editor = document.getElementById('editor'); 21 | 22 | // Not universally supported, but seems to work in iOS 7 and 8 23 | document.addEventListener("selectionchange", function() { 24 | RE.backuprange(); 25 | }); 26 | 27 | //looks specifically for a Range selection and not a Caret selection 28 | RE.rangeSelectionExists = function() { 29 | //!! coerces a null to bool 30 | var sel = document.getSelection(); 31 | if (sel && sel.type == "Range") { 32 | return true; 33 | } 34 | return false; 35 | }; 36 | 37 | RE.rangeOrCaretSelectionExists = function() { 38 | //!! coerces a null to bool 39 | var sel = document.getSelection(); 40 | if (sel && (sel.type == "Range" || sel.type == "Caret")) { 41 | return true; 42 | } 43 | return false; 44 | }; 45 | 46 | RE.editor.addEventListener("input", function() { 47 | RE.updatePlaceholder(); 48 | RE.backuprange(); 49 | RE.callback("input"); 50 | }); 51 | 52 | RE.editor.addEventListener("focus", function() { 53 | RE.backuprange(); 54 | RE.callback("focus"); 55 | }); 56 | 57 | RE.editor.addEventListener("blur", function() { 58 | RE.callback("blur"); 59 | }); 60 | 61 | RE.customAction = function(action) { 62 | RE.callback("action/" + action); 63 | }; 64 | 65 | RE.updateHeight = function() { 66 | RE.callback("updateHeight"); 67 | } 68 | 69 | RE.callbackQueue = []; 70 | RE.runCallbackQueue = function() { 71 | if (RE.callbackQueue.length === 0) { 72 | return; 73 | } 74 | 75 | setTimeout(function() { 76 | window.location.href = "re-callback://"; 77 | //window.webkit.messageHandlers.iOS_Native_FlushMessageQueue.postMessage("re-callback://") 78 | }, 0); 79 | }; 80 | 81 | RE.getCommandQueue = function() { 82 | var commands = JSON.stringify(RE.callbackQueue); 83 | RE.callbackQueue = []; 84 | return commands; 85 | }; 86 | 87 | RE.callback = function(method) { 88 | RE.callbackQueue.push(method); 89 | RE.runCallbackQueue(); 90 | }; 91 | 92 | RE.setHtml = function(contents) { 93 | var tempWrapper = document.createElement('div'); 94 | tempWrapper.innerHTML = contents; 95 | var images = tempWrapper.querySelectorAll("img"); 96 | 97 | for (var i = 0; i < images.length; i++) { 98 | images[i].onload = RE.updateHeight; 99 | } 100 | 101 | RE.editor.innerHTML = tempWrapper.innerHTML; 102 | RE.updatePlaceholder(); 103 | }; 104 | 105 | RE.getHtml = function() { 106 | return RE.editor.innerHTML; 107 | }; 108 | 109 | RE.getText = function() { 110 | return RE.editor.innerText; 111 | }; 112 | 113 | RE.setBaseTextColor = function(color) { 114 | RE.editor.style.color = color; 115 | }; 116 | 117 | RE.setPlaceholderText = function(text) { 118 | RE.editor.setAttribute("placeholder", text); 119 | }; 120 | 121 | RE.updatePlaceholder = function() { 122 | if (RE.editor.innerHTML.indexOf('img') !== -1 || RE.editor.innerHTML.length > 0) { 123 | RE.editor.classList.remove("placeholder"); 124 | } else { 125 | RE.editor.classList.add("placeholder"); 126 | } 127 | }; 128 | 129 | RE.removeFormat = function() { 130 | document.execCommand('removeFormat', false, null); 131 | }; 132 | 133 | RE.setFontSize = function(size) { 134 | RE.editor.style.fontSize = size; 135 | }; 136 | 137 | RE.setBackgroundColor = function(color) { 138 | RE.editor.style.backgroundColor = color; 139 | }; 140 | 141 | RE.setHeight = function(size) { 142 | RE.editor.style.height = size; 143 | }; 144 | 145 | RE.undo = function() { 146 | document.execCommand('undo', false, null); 147 | }; 148 | 149 | RE.redo = function() { 150 | document.execCommand('redo', false, null); 151 | }; 152 | 153 | RE.setBold = function() { 154 | document.execCommand('bold', false, null); 155 | }; 156 | 157 | RE.setItalic = function() { 158 | document.execCommand('italic', false, null); 159 | }; 160 | 161 | RE.setSubscript = function() { 162 | document.execCommand('subscript', false, null); 163 | }; 164 | 165 | RE.setSuperscript = function() { 166 | document.execCommand('superscript', false, null); 167 | }; 168 | 169 | RE.setStrikeThrough = function() { 170 | document.execCommand('strikeThrough', false, null); 171 | }; 172 | 173 | RE.setUnderline = function() { 174 | document.execCommand('underline', false, null); 175 | }; 176 | 177 | RE.setTextColor = function(color) { 178 | RE.restorerange(); 179 | document.execCommand("styleWithCSS", null, true); 180 | document.execCommand('foreColor', false, color); 181 | document.execCommand("styleWithCSS", null, false); 182 | }; 183 | 184 | RE.setTextBackgroundColor = function(color) { 185 | RE.restorerange(); 186 | document.execCommand("styleWithCSS", null, true); 187 | document.execCommand('hiliteColor', false, color); 188 | document.execCommand("styleWithCSS", null, false); 189 | }; 190 | 191 | RE.setHeading = function(heading) { 192 | document.execCommand('formatBlock', false, ''); 193 | }; 194 | 195 | RE.setIndent = function() { 196 | document.execCommand('indent', false, null); 197 | }; 198 | 199 | RE.setOutdent = function() { 200 | document.execCommand('outdent', false, null); 201 | }; 202 | 203 | RE.setOrderedList = function() { 204 | document.execCommand('insertOrderedList', false, null); 205 | }; 206 | 207 | RE.setUnorderedList = function() { 208 | document.execCommand('insertUnorderedList', false, null); 209 | }; 210 | 211 | RE.setJustifyLeft = function() { 212 | document.execCommand('justifyLeft', false, null); 213 | }; 214 | 215 | RE.setJustifyCenter = function() { 216 | document.execCommand('justifyCenter', false, null); 217 | }; 218 | 219 | RE.setJustifyRight = function() { 220 | document.execCommand('justifyRight', false, null); 221 | }; 222 | 223 | RE.getLineHeight = function() { 224 | return RE.editor.style.lineHeight; 225 | }; 226 | 227 | RE.setLineHeight = function(height) { 228 | RE.editor.style.lineHeight = height; 229 | }; 230 | 231 | RE.insertImage = function(url, alt) { 232 | var img = document.createElement('img'); 233 | img.setAttribute("src", url); 234 | img.setAttribute("alt", alt); 235 | img.onload = RE.updateHeight; 236 | 237 | RE.insertHTML(img.outerHTML); 238 | RE.callback("input"); 239 | }; 240 | 241 | RE.setBlockquote = function() { 242 | document.execCommand('formatBlock', false, '
'); 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 = ""; 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 | 5 | 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 | 26 | 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 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | Generated by CocoaPods - https://cocoapods.org 18 | Title 19 | 20 | Type 21 | PSGroupSpecifier 22 | 23 | 24 | StringsTable 25 | Acknowledgements 26 | Title 27 | Acknowledgements 28 | 29 | 30 | -------------------------------------------------------------------------------- /RichEditorViewSample/Pods/Target Support Files/Pods-RichEditorViewSample/Pods-RichEditorViewSample-dummy.m: -------------------------------------------------------------------------------- 1 | #import 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 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /RichEditorViewSample/RichEditorViewSample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /RichEditorViewSample/RichEditorViewSample.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /RichEditorViewSample/RichEditorViewSample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 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 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /RichEditorViewSample/RichEditorViewSample/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /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 | 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 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 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 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /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 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /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 --------------------------------------------------------------------------------