├── .gitignore
├── .swiftlint.yml
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── .travis.yml
├── AttributedTextView.playground
├── Contents.swift
├── contents.xcplayground
├── playground.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── timeline.xctimeline
├── AttributedTextView.podspec
├── AttributedTextView.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── xcshareddata
│ └── xcschemes
│ ├── AttributedTextView-iOS.xcscheme
│ ├── AttributedTextView-macOS.xcscheme
│ ├── AttributedTextView-tvOS.xcscheme
│ └── AttributedTextView-watchOS.xcscheme
├── AttributedTextView.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── Cartfile
├── Cartfile.private
├── Cartfile.resolved
├── Demo
├── AppDelegate.swift
├── Assets.xcassets
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── AttributedLabelSubclassDemo.swift
├── AttributedTextViewSubclassDemo.swift
├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
├── Info.plist
├── ViewController.swift
├── check.png
├── cross.png
└── warning.png
├── LICENSE
├── Package.swift
├── Readme.md
├── Screenshots
├── IconList.png
├── Sample1.png
├── Sample2.png
├── Sample3.png
├── Sample4.png
├── Sample5.png
└── Sample6.png
├── Sources
├── AttributedLabel.swift
├── AttributedTextView.h
├── AttributedTextView.swift
├── Attributer.swift
├── CGFloatDP.swift
├── Info-tvOS.plist
├── Info.plist
├── NSAttributedString+Html.swift
├── String+Attributer.swift
└── String+NSRange.swift
└── scripts
└── bootstrap
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | build/
4 | *.pbxuser
5 | !default.pbxuser
6 | *.mode1v3
7 | !default.mode1v3
8 | *.mode2v3
9 | !default.mode2v3
10 | *.perspectivev3
11 | !default.perspectivev3
12 | xcuserdata
13 | *.xccheckout
14 | *.moved-aside
15 | DerivedData
16 | *.hmap
17 | *.ipa
18 | *.xcuserstate
19 |
20 | # CocoaPods
21 | #
22 | # We recommend against adding the Pods directory to your .gitignore. However
23 | # you should judge for yourself, the pros and cons are mentioned at:
24 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
25 | #
26 | Pods/
27 |
28 | # Carthage
29 | #
30 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
31 | # Carthage/Checkouts
32 | Carthage/Build
33 |
--------------------------------------------------------------------------------
/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | disabled_rules:
2 | - trailing_whitespace
3 | - variable_name
4 | - type_name
5 | - variable_name_min_length
6 | - type_body_length
7 |
8 | #- opening_brace
9 | #- comma
10 | #- colon
11 | #- control_statement
12 | #- statement_position
13 |
14 | #opt_in_rules:
15 | #- empty_count
16 | #- missing_docs
17 |
18 |
19 | excluded:
20 | - Pods/
21 | - Sparen/Common/Generated/
22 | - Sparen/Resources
23 | - GlobalAPIKey
24 | - _name
25 |
26 | force_cast: warning
27 |
28 | force_try: warning
29 |
30 | function_parameter_count:
31 | - 5
32 | - 12
33 |
34 | function_body_length:
35 | - 50
36 | - 120
37 |
38 | cyclomatic_complexity:
39 | - 20
40 | - 60
41 |
42 | line_length:
43 | - 200
44 | - 800
45 |
46 | type_body_length:
47 | - 500
48 | - 1000
49 |
50 | file_length:
51 | - 500
52 | - 2000
53 |
54 | type_name:
55 | min_length:
56 | warning: 5
57 | error: 2
58 | max_length:
59 | warning: 40
60 | error: 60
61 |
62 | variable_name:
63 | min_length:
64 | warning: 5
65 | error: 2
66 | max_length:
67 | warning: 80
68 | error: 50
69 |
70 | #reporter: "csv" # reporter type (xcode, json, csv, checkstyle)
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: objective-c
2 | osx_image: xcode8
3 |
4 | env:
5 | global:
6 | - LC_CTYPE=en_US.UTF-8
7 | - LANG=en_US.UTF-8
8 | - WORKSPACE=EVReflection.xcworkspace
9 | - IOS_FRAMEWORK_SCHEME="EVReflection iOS"
10 | - MACOS_FRAMEWORK_SCHEME="EVReflection OSX"
11 | - TVOS_FRAMEWORK_SCHEME="EVReflection TVOS"
12 | - WATCHOS_FRAMEWORK_SCHEME="EVReflection WatchOS"
13 | - IOS_SDK=iphonesimulator10.0
14 | - MACOS_SDK=macosx10.12
15 | - TVOS_SDK=appletvsimulator10.0
16 | - WATCHOS_SDK=watchsimulator3.0
17 |
18 | matrix:
19 | - DESTINATION="OS=3.0,name=Apple Watch - 42mm" SCHEME="$WATCHOS_FRAMEWORK_SCHEME" SDK="$WATCHOS_SDK" RUN_TESTS="NO" BUILD_EXAMPLE="NO" POD_LINT="NO"
20 | - DESTINATION="OS=2.0,name=Apple Watch - 42mm" SCHEME="$WATCHOS_FRAMEWORK_SCHEME" SDK="$WATCHOS_SDK" RUN_TESTS="NO" BUILD_EXAMPLE="NO" POD_LINT="NO"
21 |
22 | - DESTINATION="OS=10.0,name=iPhone 7 Plus" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="YES"
23 | - DESTINATION="OS=9.0,name=iPhone 5" SCHEME="$IOS_FRAMEWORK_SCHEME" SDK="$IOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="YES" POD_LINT="NO"
24 |
25 | - DESTINATION="OS=10.0,name=Apple TV 1080p" SCHEME="$TVOS_FRAMEWORK_SCHEME" SDK="$TVOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="NO" POD_LINT="NO"
26 | - DESTINATION="OS=9.0,name=Apple TV 1080p" SCHEME="$TVOS_FRAMEWORK_SCHEME" SDK="$TVOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="NO" POD_LINT="NO"
27 |
28 | - DESTINATION="arch=x86_64" SCHEME="$MACOS_FRAMEWORK_SCHEME" SDK="$MACOS_SDK" RUN_TESTS="YES" BUILD_EXAMPLE="NO" POD_LINT="NO"
29 |
30 | before_install:
31 | - gem install cocoapods --pre --no-rdoc --no-ri --no-document --quiet
32 |
33 | script:
34 | - set -o pipefail
35 | - xcodebuild -version
36 | - xcodebuild -showsdks
37 |
38 | # Build Framework in Debug and Run Tests if specified
39 | - if [ $RUN_TESTS == "YES" ]; then
40 | xcodebuild -workspace "$WORKSPACE" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO ENABLE_TESTABILITY=YES test | xcpretty;
41 | else
42 | xcodebuild -workspace "$WORKSPACE" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO build | xcpretty;
43 | fi
44 |
45 | # Build Framework in Release and Run Tests if specified
46 | - if [ $RUN_TESTS == "YES" ]; then
47 | xcodebuild -workspace "$WORKSPACE" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Release ONLY_ACTIVE_ARCH=NO ENABLE_TESTABILITY=YES test | xcpretty;
48 | else
49 | xcodebuild -workspace "$WORKSPACE" -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Release ONLY_ACTIVE_ARCH=NO build | xcpretty;
50 | fi
51 |
52 | # Run `pod lib lint` if specified
53 | - if [ $POD_LINT == "YES" ]; then
54 | pod lib lint;
55 | fi
56 |
57 |
--------------------------------------------------------------------------------
/AttributedTextView.playground/Contents.swift:
--------------------------------------------------------------------------------
1 | //: Playground - noun: a place where people can play.
2 |
3 | // Since interaction is not working, clicking the links won't do anything.
4 | // Please build the project first... First time rendering can take a minute
5 |
6 |
7 | import AttributedTextView
8 | import UIKit
9 |
10 | // In interfacebuilder put an UITextView on the canvas and set the base class to AttributedTextView. For here we just create a new instance.
11 | var textView1: AttributedTextView = AttributedTextView()
12 |
13 | // First basic test
14 | textView1.attributer =
15 | "1. ".red
16 | .append("This is the first test. ").green
17 | .append("Click on ").black
18 | .append("evict.nl").makeInteract { _ in
19 | UIApplication.shared.open(URL(string: "http://evict.nl")!, options: [:], completionHandler: { completed in })
20 | }.underline
21 | + " for testing links. ".black
22 | + "Next test".underline.makeInteract { _ in
23 | print("NEXT")
24 | }
25 | .all.font(UIFont(name: "SourceSansPro-Regular", size: 16))
26 | .setLinkColor(UIColor.purple) // Does not work in playground
27 | var attributedText = textView1.attributedText // Vieuw the details for this -->
28 |
29 | // You can also use the Attributer for your UILabel. You only can't use the makeInteract function
30 | let myUILabel = UILabel()
31 | myUILabel.attributedText = ("Just ".red + "some ".green + "text.".orange).attributedText
32 | let showInPlayground = myUILabel.attributedText // Vieuw the details for this -->
33 |
34 | // Some more attributes and now using + instead of .append
35 | textView1.attributer =
36 | "2. red, ".red.underline.underline(0x00ff00)
37 | + "green, ".green.fontName("Helvetica").size(30)
38 | + "cyan, ".cyan.size(22)
39 | + "orange, ".orange.kern(10)
40 | + "blue, ".blue.strikethrough(3).baselineOffset(8)
41 | + "black.".shadow(color: UIColor.gray, offset: CGSize(width: 2, height: 3), blurRadius: 3.0)
42 | attributedText = textView1.attributedText // Vieuw the details for this -->
43 |
44 | // Match or matchAll
45 | textView1.attributer = "It is this or it is that where the word is is selected".size(20)
46 | .match("is").underline.underline(UIColor.red)
47 | .matchAll("is").strikethrough(4)
48 | attributedText = textView1.attributedText // Vieuw the details for this -->
49 |
50 | // Match or matchAny
51 | textView1.attributer = "The quick brown fox jumps over the lazy dog".size(20)
52 | .matchAny(["quick", "brown", "lazy"]).underline.underline(UIColor.red)
53 | attributedText = textView1.attributedText // Vieuw the details for this -->
54 |
55 |
56 | // Select hashtags or mentions
57 | textView1.attributer = "@test: What #hashtags do we have in @evermeer #AtributedTextView library"
58 | .matchHashtags.underline
59 | .matchMentions
60 | .makeInteract { link in
61 | UIApplication.shared.open(URL(string: "https://twitter.com\(link.replacingOccurrences(of: "@", with: ""))")!, options: [:], completionHandler: { completed in })
62 | }
63 | .setLinkColor(UIColor.red) // Does not work in playground
64 | attributedText = textView1.attributedText // Vieuw the details for this -->
65 |
66 | // Select links
67 | textView1.attributer = "link to http://evict.nl and https://github.com/evermeer"
68 | .matchLinks
69 | .makeInteract { link in
70 | UIApplication.shared.open(URL(string: link)!, options: [:], completionHandler: { completed in })
71 | }
72 | attributedText = textView1.attributedText // View the details for this -->
73 |
74 | textView1.attributer = (
75 | "test stroke".strokeWidth(2).strokeColor(UIColor.red).paragraphAlignCenter.paragraphApplyStyling
76 | + "test stroke\n".strokeWidth(2).strokeColor(UIColor.blue)
77 | + "test strikethrough".strikethrough(2).strikethroughColor(UIColor.red)
78 | + " test strikethrough 2\n".strikethrough(2).strikethroughColor(UIColor.yellow)
79 | + "expansion\n".expansion(0.8).paragraphAlignRight.paragraphApplyStyling
80 | + ("letterpress ".letterpress
81 | + " obliquenes\n".obliqueness(0.4).backgroundColor(UIColor.cyan)).paragraphAlignLeft.paragraphApplyStyling
82 | ).all.size(24)
83 | attributedText = textView1.attributedText // View the details for this -->
84 |
85 | textView1.attributer = (
86 | "ligature fi and fl = ".ligature(0) + "fi and fl\n".ligature(1)
87 | + "0123456789".writingDirection([3])
88 | ).all.fontName("Hoefler Text").size(24)
89 | attributedText = textView1.attributedText // View the details for this -->
90 |
91 | textView1.attributer = (
92 | "The quick brown fox jumps over the lazy dog.\nPack my box with five dozen liquor jugs.\nSeveral fabulous dixieland jazz groups played with quick tempo.".paragraphLineHeightMultiple(5).paragraphLineSpacing(6).paragraphMinimumLineHeight(15).paragraphMaximumLineHeight(50).paragraphLineSpacing(10).paragraphLineBreakModeWordWrapping.paragraphFirstLineHeadIndent(20).paragraphApplyStyling
93 | ).all.size(12)
94 | attributedText = textView1.attributedText // View the details for this -->
95 |
96 | let rules = "Using backslash r has a strange effect on the last url if there is nothing behind it.\r\nhttp://serverurl.com/api.html\r\nhttp://serverurl.com/api2.html\r\nhttp://serverurl.com/api3.html"
97 | textView1.attributer = rules.matchLinks
98 | .makeInteract({ link in
99 | print("\(link)")
100 | })
101 | attributedText = textView1.attributedText // View the details for this -->
102 |
103 |
104 | textView1.attributer = "And here we just do some tests with underline"
105 | .underline(.double, .patternDashDotDot)
106 | attributedText = textView1.attributedText // View the details for this -->
107 |
108 |
109 |
110 | textView1.attributer = "Edwin".red.fontName("Baskerville").size(15)
111 | .append("Edwin").gray.font(UIFont(name: "SourceSansPro-Regular", size: 15))
112 | .append("Edwin").makeInteract({ _ in
113 | }).fontName("Helvetica").size(15)
114 | .setLinkColor(.orange)
115 | attributedText = textView1.attributedText // View the details for this -->
116 |
117 |
118 |
119 | textView1.attributer = "My name is: Edwin
With a bulet list
".html
120 | attributedText = textView1.attributedText // View the details for this -->
121 |
--------------------------------------------------------------------------------
/AttributedTextView.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/AttributedTextView.playground/playground.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/AttributedTextView.playground/playground.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/AttributedTextView.playground/timeline.xctimeline:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
9 |
10 |
14 |
15 |
19 |
20 |
24 |
25 |
29 |
30 |
34 |
35 |
39 |
40 |
44 |
45 |
49 |
50 |
54 |
55 |
59 |
60 |
64 |
65 |
69 |
70 |
74 |
75 |
79 |
80 |
84 |
85 |
89 |
90 |
94 |
95 |
99 |
100 |
104 |
105 |
109 |
110 |
114 |
115 |
119 |
120 |
124 |
125 |
130 |
131 |
135 |
136 |
140 |
141 |
145 |
146 |
150 |
151 |
155 |
156 |
160 |
161 |
165 |
166 |
170 |
171 |
175 |
176 |
177 |
178 |
--------------------------------------------------------------------------------
/AttributedTextView.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'AttributedTextView'
3 | s.version = '1.4.1'
4 | s.license = { :type => "MIT", :file => "LICENSE" }
5 | s.summary = 'Easiest way to create an attributed UILabel or UITextView (with support for multiple links and HTML)'
6 | s.homepage = 'https://github.com/evermeer/AttributedTextView'
7 | s.social_media_url = 'https://twitter.com/evermeer'
8 | s.authors = { "Edwin Vermeer" => "edwin@evict.nl" }
9 | s.source = { :git => "https://github.com/evermeer/AttributedTextView.git", :tag => "v"+s.version.to_s }
10 | s.platforms = { :ios => "8.0" }
11 | s.requires_arc = true
12 | s.pod_target_xcconfig = { 'SWIFT_VERSION' => '5.0' }
13 | s.swift_version = '5.0'
14 | s.swift_versions = ['4.0', '4.2', '5.0']
15 |
16 | s.frameworks = "Foundation", "UIKit"
17 | s.source_files = "Sources/*.swift"
18 | end
19 |
--------------------------------------------------------------------------------
/AttributedTextView.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/AttributedTextView.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/AttributedTextView.xcodeproj/xcshareddata/xcschemes/AttributedTextView-iOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
49 |
50 |
51 |
52 |
53 |
54 |
64 |
65 |
71 |
72 |
73 |
74 |
75 |
76 |
82 |
83 |
89 |
90 |
91 |
92 |
94 |
95 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/AttributedTextView.xcodeproj/xcshareddata/xcschemes/AttributedTextView-macOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
49 |
50 |
51 |
52 |
53 |
54 |
64 |
65 |
71 |
72 |
73 |
74 |
75 |
76 |
82 |
83 |
89 |
90 |
91 |
92 |
94 |
95 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/AttributedTextView.xcodeproj/xcshareddata/xcschemes/AttributedTextView-tvOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
49 |
50 |
51 |
52 |
53 |
54 |
64 |
65 |
71 |
72 |
73 |
74 |
75 |
76 |
82 |
83 |
89 |
90 |
91 |
92 |
94 |
95 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/AttributedTextView.xcodeproj/xcshareddata/xcschemes/AttributedTextView-watchOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
34 |
35 |
45 |
46 |
52 |
53 |
54 |
55 |
56 |
57 |
63 |
64 |
70 |
71 |
72 |
73 |
75 |
76 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/AttributedTextView.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
12 |
13 |
15 |
16 |
18 |
19 |
21 |
22 |
24 |
25 |
27 |
28 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/AttributedTextView.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Cartfile:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evermeer/AttributedTextView/c937141724bb797ea29ea4b7055fa78f10911f36/Cartfile
--------------------------------------------------------------------------------
/Cartfile.private:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evermeer/AttributedTextView/c937141724bb797ea29ea4b7055fa78f10911f36/Cartfile.private
--------------------------------------------------------------------------------
/Cartfile.resolved:
--------------------------------------------------------------------------------
1 | github "Quick/Nimble" "v5.1.1"
2 | github "Quick/Quick" "v0.10.0"
3 |
--------------------------------------------------------------------------------
/Demo/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Demo
4 | //
5 | // Created by Edwin Vermeer on 29/11/2016.
6 | // Copyright © 2016 evermeer. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
17 | return true
18 | }
19 | }
20 |
21 |
--------------------------------------------------------------------------------
/Demo/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | }
88 | ],
89 | "info" : {
90 | "version" : 1,
91 | "author" : "xcode"
92 | }
93 | }
--------------------------------------------------------------------------------
/Demo/AttributedLabelSubclassDemo.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AttributedLabelSubclassDemo.swift
3 | // AttributedTextView
4 | //
5 | // Created by Edwin Vermeer on 03/03/2017.
6 | // Copyright © 2017 evermeer. All rights reserved.
7 | //
8 |
9 | import AttributedTextView
10 | import UIKit
11 |
12 | @IBDesignable open class AttributedLabelSubclassDemo: AttributedLabel {
13 |
14 | // Add these fields in interfacebuilder and make sure the interface is updated after changes
15 |
16 | // This field is used as the field seperator for the other fields
17 | @IBInspectable var seperator: String? {
18 | didSet { configureAttributedLabel() }
19 | }
20 |
21 | // Enter here the texts for all itmes seperated by the seperator field
22 | @IBInspectable var multiTexts: String? {
23 | didSet { configureAttributedLabel() }
24 | }
25 |
26 | // Enter here the words that will be highlighted seperated by the seperator field
27 | @IBInspectable var multiHighlights: String? {
28 | didSet { configureAttributedLabel() }
29 | }
30 |
31 | // Enter here all values for the icons. 0 = okImage, 1 = notOKImage, otherwise warningImage
32 | @IBInspectable var multiIconState: String? {
33 | didSet { configureAttributedLabel() }
34 | }
35 |
36 | // This image is used for icon state 1
37 | @IBInspectable var okImage: UIImage? {
38 | didSet { configureAttributedLabel() }
39 | }
40 |
41 | // This image is used for icon state 0
42 | @IBInspectable var notOkImage: UIImage? {
43 | didSet { configureAttributedLabel() }
44 | }
45 |
46 | // This image is used for icon states that are not 0 or 1
47 | @IBInspectable var warningImage: UIImage? {
48 | didSet { configureAttributedLabel() }
49 | }
50 |
51 | // Configure our custom styling.
52 | override open func configureAttributedLabel() {
53 |
54 | // First make sure all fields are ok and complete
55 | self.attributedText = Attributer("Incomplete settings.").attributedText
56 | self.numberOfLines = 0
57 | guard let seperator = self.seperator else { return }
58 | guard let texts: [String] = multiTexts?.components(separatedBy: seperator) else { return }
59 | guard let highlights: [String] = multiHighlights?.components(separatedBy: seperator) else { return }
60 | guard let states: [String] = multiIconState?.components(separatedBy: seperator) else { return }
61 | guard let okImage = self.okImage else { return }
62 | guard let notOkImage = self.notOkImage else { return }
63 | guard let warningImage = self.warningImage else { return }
64 | if texts.count != highlights.count || texts.count != states.count { return }
65 |
66 | // Start with an empty Attributer
67 | var attr = Attributer("")
68 |
69 | // The icon needs this bounds in order to position it correctly
70 | let rect = CGRect(x: 0, y: -4, width: 20, height: 20)
71 |
72 | // loop through the items and buildup the bulit list
73 | for (index, state) in states.enumerated() {
74 |
75 | // What image do you want for this item
76 | let image = (state == "1" ? okImage : (state == "0" ? notOkImage : warningImage))
77 |
78 | // Build up a line and append it
79 | attr = attr.append(
80 |
81 | // The image
82 | Attributer(image, bounds: rect)
83 |
84 | // The text in green
85 | .append(" " + texts[index]).green.append("\n")
86 |
87 | // select the highlight text and make it red
88 | .match(highlights[index]).red
89 | )
90 | }
91 |
92 | // apply the correct paragraph layout and set the attributedText
93 | self.attributedText = attr.all.paragraphHeadIndent(34) // Make sure a second line is alligned
94 | .paragraphLineSpacing(-2) // a little less than default spacing between lines
95 | .paragraphSpacing(15) // The space between items
96 | .paragraphApplyStyling.attributedText // apply the styling and put it in the attributedText
97 |
98 | // Just to be sure
99 | layoutIfNeeded()
100 | }
101 | }
102 |
103 |
--------------------------------------------------------------------------------
/Demo/AttributedTextViewSubclassDemo.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AttributedTextViewSubclassDemo.swift
3 | // AttributedTextView
4 | //
5 | // Created by Edwin Vermeer on 03/03/2017.
6 | // Copyright © 2017 evermeer. All rights reserved.
7 | //
8 |
9 | import AttributedTextView
10 | import UIKit
11 |
12 | @IBDesignable class AttributedTextViewSubclassDemo: AttributedTextView {
13 |
14 | // Add this field in interfacebuilder and make sure the interface is updated after changes
15 | @IBInspectable var linkText: String? {
16 | didSet { configureAttributedTextView() }
17 | }
18 |
19 | // Add this field in interfacebuilder and make sure the interface is updated after changes
20 | @IBInspectable var linkUrl: String? {
21 | didSet { configureAttributedTextView() }
22 | }
23 |
24 | // Configure our custom styling.
25 | override func configureAttributedTextView() {
26 | if let text = self.text, let linkText = self.linkText, let linkUrl = self.linkUrl {
27 | self.attributer = text.green.match(linkText).makeInteract { _ in
28 | if #available(iOS 10, *) {
29 | UIApplication.shared.open(URL(string: linkUrl)!, options: [:], completionHandler: { completed in })
30 | } else {
31 | _ = UIApplication.shared.openURL(URL(string: linkUrl)!)
32 | }
33 | }
34 | } else {
35 | self.attributer = (self.text ?? "").green
36 | }
37 | layoutIfNeeded()
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Demo/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Demo/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 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/Demo/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIMainStoryboardFile
26 | Main
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/Demo/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // Demo
4 | //
5 | // Created by Edwin Vermeer on 29/11/2016.
6 | // Copyright © 2016 evermeer. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import AttributedTextView
11 |
12 |
13 | class ViewController: UIViewController {
14 |
15 | @IBOutlet weak var attributedTextView: AttributedTextView!
16 |
17 | // These are the samples for this demo
18 | lazy var samples: [(title: String, show: (()->()))] = [
19 | ("custom link and attributes", showSample1),
20 | ("coloring and aditional attributes", showSample2),
21 | ("single or multiple matches", showSample3),
22 | ("hashtags and mentions", showSample4),
23 | ("creating your own composit style", showSample5),
24 | ("just some more styles", showSample6),
25 | ("paragraph formatting", showSample7),
26 | ("more paragraph formatting", showSample8)
27 | ]
28 |
29 | // For more basic tests about how to use AttributedTextView, see the playground
30 |
31 | override func viewDidAppear(_ animated: Bool) {
32 | showSample1()
33 | }
34 |
35 | // Dynamically build the header and the previous and next links around the content
36 | func decorate(_ id: Int, _ builder: ((_ content: Attributer) -> Attributer)) -> Attributer {
37 | var b = "Sample \(id + 1) of \(samples.count): \(samples[id].title)\n\n".red
38 | if id > 0 {
39 | b = b + "<-- previous sample\n\n".underline.makeInteract { _ in
40 | self.samples[id - 1].show()
41 | }
42 | }
43 | b = builder(b) // Now add the content
44 | if id < (samples.count - 1) {
45 | b = b + "\n\nnext sample -->".underline.makeInteract { _ in
46 | self.samples[id + 1].show()
47 | }
48 | }
49 | return b
50 | }
51 |
52 | // Basic test where we use the .append to paste the attributed string parts together
53 | func showSample1() {
54 | attributedTextView.attributer = decorate(0) { content in return content
55 | .append("This is the first test. ").green
56 | .append("Tap on ").black
57 | .append("evict.nl").makeInteract { _ in
58 | if #available(iOS 10, *) {
59 | UIApplication.shared.open(URL(string: "http://evict.nl")!, options: [:], completionHandler: { (completed) in })
60 | } else {
61 | _ = UIApplication.shared.openURL(URL(string: "http://evict.nl")!)
62 | }
63 | }.underline
64 | .append(" for testing links. Or tap on the 'next sample' link below ").black
65 | .underline(.double, .patternDashDotDot)
66 | .append("[test]").makeInteract { text in
67 | print("makeInteract : \(text)")
68 | }
69 | }
70 | .all.font(UIFont(name: "SourceSansPro-Regular", size: 16)) // Font not availabel in this demo...
71 | .setLinkColor(UIColor.purple) // Will be set on the control so we also have to reset it when we show the next sample.
72 | }
73 |
74 | // Basic test where we use + to paste the attributed string parts together
75 | func showSample2() {
76 | attributedTextView.attributer = decorate(1) { content in return content
77 | + "green, ".green.fontName("Helvetica").size(30)
78 | + "cyan, ".cyan.size(22)
79 | + "orange, ".orange.kern(10)
80 | + "blue, ".blue.strikethrough(3).baselineOffset(8)
81 | + "black.".shadow(color: UIColor.gray, offset: CGSize(width: 2, height: 3), blurRadius: 3.0)
82 | }
83 | .setLinkColor(UIColor.blue) // Reset the link color to the default blue
84 | }
85 |
86 | // We can select 1 or more ranges in an attributed text and apply a style to that.
87 | func showSample3() {
88 | attributedTextView.attributer = decorate(2) { content in return content
89 | + "It is this or it is that where the word is is selected\n\n".size(20)
90 | .match("is").underline
91 | .underline(UIColor.red)
92 | .matchAll("is").strikethrough(4)
93 | + "Select any of the qords quick, brown and lazy in: The quick brown fox jumps over the lazy dog".size(16)
94 | .matchAny(["quick", "brown", "lazy"]).underline
95 | .underline(UIColor.red)
96 | }
97 | }
98 |
99 | // There are custom matchers for hashtags, metions, links or you can use a regexp with matchPattern
100 | func showSample4() {
101 | attributedTextView.attributer = decorate(3) { content in return content
102 | + "@test: What #hashtags do we have in @evermeer #AtributedTextView library"
103 | .matchHashtags.underline
104 | .makeInteract { link in
105 | if #available(iOS 10, *) {
106 | UIApplication.shared.open(URL(string: "https://twitter.com/hashtag/\(link.replacingOccurrences(of: "%23", with: ""))")!, options: [:], completionHandler: { completed in })
107 | } else {
108 | _ = UIApplication.shared.openURL(URL(string: "https://twitter.com/hashtag/\(link.replacingOccurrences(of: "%23", with: ""))")!)
109 | }
110 | }
111 | .matchMentions
112 | .makeInteract { link in
113 | if #available(iOS 10, *) {
114 | UIApplication.shared.open(URL(string: "https://twitter.com/\(link.replacingOccurrences(of: "%40", with: ""))")!, options: [:], completionHandler: { completed in })
115 | } else {
116 | _ = UIApplication.shared.openURL(URL(string: "https://twitter.com/\(link.replacingOccurrences(of: "%40", with: ""))")!)
117 | }
118 | }
119 | }
120 | }
121 |
122 | // Here we simply use our own composit style
123 | func showSample5() {
124 | attributedTextView.attributer = decorate(4) { content in return content
125 | + "This is our custom title".myTitle
126 | }
127 | }
128 |
129 | // just some other styles
130 | func showSample6() {
131 | attributedTextView.attributer = decorate(5) { content in return (content
132 | + ( "test stroke".strokeWidth(2).strokeColor(UIColor.red).paragraphAlignCenter.paragraphApplyStyling
133 | .append("test stroke\n").strokeWidth(2).strokeColor(UIColor.blue)
134 | .append("test strikethrough").strikethrough(2).strikethroughColor(UIColor.red)
135 | .append(" test strikethrough\n").strikethrough(2).strikethroughColor(UIColor.yellow)
136 | + "expansion\n".expansion(0.8).paragraphAlignRight.paragraphApplyStyling
137 | + ("letterpress ".letterpress
138 | + " obliquenes\n".obliqueness(0.4).backgroundColor(UIColor.cyan)).paragraphAlignRight.paragraphApplyStyling
139 | ).all.size(24)
140 | )
141 | }
142 | }
143 |
144 | func showSample7() {
145 | attributedTextView.attributer = decorate(6) { content in return (content
146 | + ( "The quick brown fox jumps over the lazy dog.\nPack my box with five dozen liquor jugs.\nSeveral fabulous dixieland jazz groups played with quick tempo.".paragraphLineHeightMultiple(2).paragraphLineSpacing(5).paragraphMinimumLineHeight(15).paragraphMaximumLineHeight(30).paragraphLineSpacing(10).paragraphLineBreakModeWordWrapping.paragraphFirstLineHeadIndent(20).paragraphApplyStyling
147 | ).all.size(12)
148 | )
149 | }
150 | }
151 |
152 | func showSample8() {
153 | attributedTextView.attributer = decorate(7) { content in return (content + (
154 | "The quick brown fox jumps over the lazy dog.\nPack my box with five dozen liquor jugs.\nSeveral fabulous dixieland jazz groups played with quick tempo.".brown
155 | .match("brown fox").underline.makeInteract { (link) in
156 | print("TODO: open terms of user screen")
157 | }
158 | .match("fabulous dixieland").underline.makeInteract { (link) in
159 | print("TODO: open privacy policy")
160 | }.all.paragraphAlignRight.paragraphApplyStyling
161 | ))
162 | }
163 | }
164 |
165 | }
166 |
167 |
168 | // Extending the Attributer and String so that it supports a custom composit style. See the showSample5
169 |
170 | extension Attributer {
171 | open var myTitle: Attributer {
172 | get {
173 | return self.fontName("Arial").size(28).color(0xffaa66).kern(5)
174 | }
175 | }
176 | }
177 |
178 | public extension String {
179 | var myTitle: Attributer {
180 | get {
181 | return attributer.myTitle
182 | }
183 | }
184 | }
185 |
186 |
--------------------------------------------------------------------------------
/Demo/check.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evermeer/AttributedTextView/c937141724bb797ea29ea4b7055fa78f10911f36/Demo/check.png
--------------------------------------------------------------------------------
/Demo/cross.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evermeer/AttributedTextView/c937141724bb797ea29ea4b7055fa78f10911f36/Demo/cross.png
--------------------------------------------------------------------------------
/Demo/warning.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evermeer/AttributedTextView/c937141724bb797ea29ea4b7055fa78f10911f36/Demo/warning.png
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT 3 License
2 |
3 | Copyright (c) 2016, EVICT B.V.
4 | All rights reserved.
5 | http://evict.nl, mailto://edwin@evict.nl
6 |
7 | Redistribution and use in source and binary forms, with or without
8 | modification, are permitted provided that the following conditions are met:
9 | * Redistributions of source code must retain the above copyright
10 | notice, this list of conditions and the following disclaimer.
11 | * Redistributions in binary form must reproduce the above copyright
12 | notice, this list of conditions and the following disclaimer in the
13 | documentation and/or other materials provided with the distribution.
14 | * Neither the name of EVICT B.V. nor the
15 | names of its contributors may be used to endorse or promote products
16 | derived from this software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY
22 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.5
2 | //
3 | // AttributedTextView.swift
4 | // Restofire
5 | //
6 | // Created by Edwin Vermeer on 23/10/15.
7 | // Copyright © 2016 evermeer. All rights reserved.
8 |
9 | import PackageDescription
10 |
11 | let packageName = "AttributedTextView"
12 | let package = Package(
13 | name: packageName,
14 | platforms: [
15 | .iOS(.v8),
16 | ],
17 | products: [
18 | .library(
19 | name: packageName,
20 | targets: [packageName]
21 | )
22 | ],
23 | dependencies: [
24 | // Stub
25 | ],
26 | targets: [
27 | .target(
28 | name: packageName,
29 | path: "Sources"
30 | ),
31 | ]
32 | )
33 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | ## AttributedTextView
2 |
3 | [](https://github.com/evermeer/AttributedTextView/issues)
4 | [](http://cocoadocs.org/docsets/AttributedTextView)
5 | [](https://github.com/evermeer/AttributedTextView/stargazers)
6 |
7 | [](http://cocoadocs.org/docsets/AttributedTextView)
8 | [](https://github.com/apple/swift-package-manager)
9 | [](https://github.com/Carthage/Carthage)
10 | [](https://developer.apple.com/swift)
11 | [](http://cocoadocs.org/docsets/AttributedTextView)
12 | [](http://cocoadocs.org/docsets/AttributedTextView)
13 |
14 | [](https://github.com/evermeer)
15 | [](http://twitter.com/evermeer)
16 | [](http://nl.linkedin.com/in/evermeer/en)
17 | [](http://evict.nl)
18 | [](mailto:edwin@evict.nl?SUBJECT=About%20EVReflection)
19 |
20 |
21 | Easiest way to create an attributed UITextView (with support for multiple links and html).
22 |
23 | See the demo app and the playground for detailed information how to use AttributedTextView
24 |
25 | - [Requirements](#requirements)
26 | - [Usage](#usage)
27 | - [General usage](#general-usage)
28 | - [Paragraph styling](#paragraph-styling)
29 | - [The active range](#the-active-range)
30 | - [Sample code](#sample-code)
31 | - [Use the attributedtext functionality on a UILabel](#use-the-attributedtext-functionality-on-a-uilabel)
32 | - [Extending AttributedTextView](#extending-attributedtextview)
33 | - [Decorating the Attributed object](#decorating-the-attributed-object)
34 | - [Creating your own custom label](#creating-your-own-custom-label)
35 | - [Creating your own custom textview](#creating-your-own-custom-textview)
36 | - [Installation](#installation)
37 | - [License](#license)
38 | - [My other libraries](#My-other-libraries)
39 |
40 | ## Requirements
41 |
42 | - iOS 8.0+
43 | - Xcode 8.0+
44 |
45 | ## Usage
46 |
47 | ### General usage
48 | In interfacebuilder put an UITextView on the canvas and set the base class to AttributedTextView and create a referencing outlet to the a property in your viewController. In the samples below we have called this property textView1. Always assign to the attributer property when you want to set something.
49 |
50 | ### Paragraph styling
51 | You do have to be aware that the paragraph functions will only be applied after calling the .paragraphApplyStyling function. On start the paragraph styling will use default styling. After each range change (what happens after .all, .match* or .append) the styling will be reset to the default.
52 |
53 | ### The active range
54 | Styling will always be applied on the active range. When executing a function on a string, then that complete string will become the active range. If you use .append to add an other string, then that latest string will become the active range. When using the + sign then that will replaced by an append on 2 Attributer objects. All functions on those objects will first be performed before the append will be executed. So if you do an .all then still only one of the strings will be tha active range. You can use brackets to influence the order of execution.
55 |
56 | For instance here all text will be size 20
57 |
58 | ```swift
59 | ("red".red + "blue".blue).all.size(20)
60 | ```
61 |
62 | And here only the text blue will be size 20
63 |
64 | ```swift
65 | "red".red + "blue".blue.all.size(20)
66 | ```
67 |
68 | And like this all text will be size 20
69 |
70 | ```swift
71 | "red".red.append("blue").blue.all.size(20)
72 | ```
73 |
74 | ### Clickable links
75 | When using AttributedTextView and creating links with .makeInteract, then you have to be aware that it will also automatically set the following properties which are needed for links to work.
76 |
77 | ```swift
78 | isUserInteractionEnabled = true
79 | isSelectable = true
80 | isEditable = false
81 | ```
82 |
83 | ### Sample code
84 |
85 | Here is a sample of some basic functions:
86 |
87 | ```swift
88 | textView1.attributer =
89 | "1. ".red
90 | .append("This is the first test. ").green
91 | .append("Click on ").black
92 | .append("evict.nl").makeInteract { _ in
93 | UIApplication.shared.open(URL(string: "http://evict.nl")!, options: [:], completionHandler: { completed in })
94 | }.underline
95 | .append(" for testing links. ").black
96 | .append("Next test").underline.makeInteract { _ in
97 | print("NEXT")
98 | }
99 | .all.font(UIFont(name: "SourceSansPro-Regular", size: 16))
100 | .setLinkColor(UIColor.purple)
101 | ```
102 |
103 | 
104 |
105 | Some more attributes and now using + instead of .append:
106 |
107 | ```swift
108 | textView1.attributer =
109 | "2. red, ".red.underline.underline(0x00ff00)
110 | + "green, ".green.fontName("Helvetica").size(30)
111 | + "cyan, ".cyan.size(22)
112 | + "orange, ".orange.kern(10)
113 | + "blue, ".blue.strikethrough(3).baselineOffset(8)
114 | + "black.".shadow(color: UIColor.gray, offset: CGSize(width: 2, height: 3), blurRadius: 3.0)
115 | ```
116 |
117 | 
118 |
119 | A match and matchAll sample:
120 |
121 | ```swift
122 | textView1.attributer = "It is this or it is that where the word is is selected".size(20)
123 | .match("is").underline.underline(UIColor.red)
124 | .matchAll("is").strikethrough(4)
125 | ```
126 |
127 | 
128 |
129 | A hashtags and mentions sample:
130 |
131 | ```swift
132 | textView1.attributer = "@test: What #hashtags do we have in @evermeer #AtributedTextView library"
133 | .matchHashtags.underline
134 | .matchMentions
135 | .makeInteract { link in
136 | UIApplication.shared.open(URL(string: "https://twitter.com\(link.replacingOccurrences(of: "@", with: ""))")!, options: [:], completionHandler: { completed in })
137 | }
138 | ```
139 |
140 | 
141 |
142 | A html sample:
143 |
144 | ```swift
145 | textView1.attributer = "My name is: Edwin
With a bulet list
".html
146 | ```
147 |
148 | 
149 |
150 | Some other text formating samples:
151 |
152 | ```swift
153 | textView1.attributer = (
154 | "test stroke".strokeWidth(2).strokeColor(UIColor.red)
155 | + "test stroke 2\n".strokeWidth(2).strokeColor(UIColor.blue)
156 | + "test strikethrough".strikethrough(2).strikethroughColor(UIColor.red)
157 | + " test strikethrough 2\n".strikethrough(2).strikethroughColor(UIColor.yellow)
158 | + "letterpress ".letterpress
159 | + " obliquenes\n".obliqueness(0.4).backgroundColor(UIColor.cyan)
160 | + "expansion\n".expansion(0.8)
161 | ).all.size(24)
162 | ```
163 |
164 | 
165 |
166 |
167 | Paragraph formatting:
168 |
169 | ```swift
170 | textView1.attributer = (
171 | "The quick brown fox jumps over the lazy dog.\nPack my box with five dozen liquor jugs.\nSeveral fabulous dixieland jazz groups played with quick tempo."
172 | .paragraphLineHeightMultiple(5)
173 | .paragraphLineSpacing(6)
174 | .paragraphMinimumLineHeight(15)
175 | .paragraphMaximumLineHeight(50)
176 | .paragraphLineSpacing(10)
177 | .paragraphLineBreakModeWordWrapping
178 | .paragraphFirstLineHeadIndent(20)
179 | .paragraphApplyStyling
180 | ).all.size(12)
181 | ```
182 |
183 |
184 |
185 |
186 | ### Use the attributedText functionality on a UILabel
187 | You can also use the Attributer for your UILabel. You only can't use the makeInteract function:
188 |
189 | ```swift
190 | let myUILabel = UILabel()
191 | myUILabel.attributedText = ("Just ".red + "some ".green + "text.".orange).attributedText
192 | ```
193 |
194 |
195 | ### Extending AttributedTextView
196 | In the demo app you can see how you can extend the AttributedTextView with a custom property / function that will perform multiple actions. Here is a simple sample that will show you how you can create a myTitle property that will set multiple attributes:
197 |
198 | ```swift
199 | extension Attributer {
200 | open var myTitle: Attributer {
201 | get {
202 | return self.fontName("Arial").size(28).color(0xffaa66).kern(5)
203 | }
204 | }
205 | }
206 |
207 | public extension String {
208 | var myTitle: Attributer {
209 | get {
210 | return attributer.myTitle
211 | }
212 | }
213 | }
214 | ```
215 |
216 | ### Decorating the Attributed object
217 | In the demo app there is also a sample that shows you how you could decorate an Attributed object with default styling.
218 |
219 | ```swift
220 | attributedTextView.attributer = decorate(4) { content in return content
221 | + "This is our custom title".myTitle
222 | }
223 | ```
224 |
225 | The decorate function can then look something like this:
226 |
227 | ```swift
228 | func decorate(_ id: Int, _ builder: ((_ content: Attributer) -> Attributer)) -> Attributer {
229 | var b = "Sample \(id + 1):\n\n".red
230 | b = builder(b) // Now add the content
231 | return b
232 | }
233 | ```
234 |
235 | ### Creating your own custom label
236 | There is also an AttributedLabel class which derives from UILabel which makes it easy to create your own custom control that includes support for interfacebuilder. If you put a lable on a form in interfacebuilder and set it's class to your subclass (AttributedLabelSubclassDemo in the sample below) Then you will see the text formated in interface building according to what you have implemented in the configureAttributedLabel function. Here is a sample where a highlightText property is added so that a text can be constructed where that part is highlighted.
237 |
238 | ```swift
239 | import AttributedTextView
240 | import UIKit
241 |
242 | @IBDesignable open class AttributedLabelSubclassDemo: AttributedLabel {
243 |
244 | // Add this field in interfacebuilder and make sure the interface is updated after changes
245 | @IBInspectable var highlightText: String? {
246 | didSet { configureAttributedLabel() }
247 | }
248 |
249 | // Configure our custom styling.
250 | override open func configureAttributedLabel() {
251 | self.numberOfLines = 0
252 | if let highlightText = self.highlightText {
253 | self.attributedText = self.text?.green.match(highlightText).red.attributedText
254 | } else {
255 | self.attributedText = self.text?.green.attributedText
256 | }
257 | layoutIfNeeded()
258 | }
259 | }
260 | ```
261 |
262 | In the demo app there is also a lable subclass for an icon list like this:
263 | 
264 |
265 | You can find the code here:
266 | [Icon bulet list code](https://github.com/evermeer/AttributedTextView/blob/master/Demo/AttributedLabelSubclassDemo.swift#L52)
267 |
268 |
269 | ### Creating your own custom textview
270 | You could do the same as a label with the AttributedTextView (see previous paragraph). In the sample below 2 properties are entered into interfacebuilder the linkText is the part of the text what will be clickable and linkUrl will be the webpage that will be opened.
271 |
272 | ```swift
273 | import AttributedTextView
274 | import UIKit
275 |
276 | @IBDesignable class AttributedTextViewSubclassDemo: AttributedTextView {
277 |
278 | // Add this field in interfacebuilder and make sure the interface is updated after changes
279 | @IBInspectable var linkText: String? {
280 | didSet { configureAttributedTextView() }
281 | }
282 |
283 | // Add this field in interfacebuilder and make sure the interface is updated after changes
284 | @IBInspectable var linkUrl: String? {
285 | didSet { configureAttributedTextView() }
286 | }
287 |
288 | // Configure our custom styling.
289 | override func configureAttributedTextView() {
290 | if let text = self.text, let linkText = self.linkText, let linkUrl = self.linkUrl {
291 | self.attributer = text.green.match(linkText).makeInteract { _ in
292 | UIApplication.shared.open(URL(string: linkUrl)!, options: [:], completionHandler: { completed in })
293 | }
294 | } else {
295 | self.attributer = (self.text ?? "").green
296 | }
297 | layoutIfNeeded()
298 | }
299 | }
300 | ```
301 |
302 |
303 | ## Installation
304 |
305 | ### CocoaPods
306 |
307 | [CocoaPods](http://cocoapods.org) is a dependency manager for Cocoa projects. You can install it with the following command:
308 |
309 | ```bash
310 | $ gem install cocoapods
311 | ```
312 |
313 | > CocoaPods 1.1.0+ is required to build AttributedTextView 0.1.0+.
314 |
315 | To integrate AttributedTextView into your Xcode project using CocoaPods, specify it in your `Podfile`:
316 |
317 | ```ruby
318 | source 'https://github.com/CocoaPods/Specs.git'
319 | platform :ios, '8.0'
320 | use_frameworks!
321 |
322 | pod 'AttributedTextView', '~> 0.5.1'
323 | ```
324 |
325 | Then, run the following command:
326 |
327 | ```bash
328 | $ pod install
329 | ```
330 |
331 | ### Carthage
332 |
333 | [Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that automates the process of adding frameworks to your Cocoa application.
334 |
335 | You can install Carthage with [Homebrew](http://brew.sh/) using the following command:
336 |
337 | ```bash
338 | $ brew update
339 | $ brew install carthage
340 | ```
341 |
342 | To integrate AttributedTextView into your Xcode project using Carthage, specify it in your `Cartfile`:
343 |
344 | ```ogdl
345 | github "evermeer/AttributedTextView" ~> 0.5.1
346 | ```
347 | ### Swift Package Manager
348 |
349 | To use AttributedTextView as a [Swift Package Manager](https://swift.org/package-manager/) package just add the following URL in your project file:
350 |
351 | ```
352 | https://github.com/evermeer/AttributedTextView
353 | ```
354 |
355 | And specify a version from `1.4.1` and onwards.
356 |
357 | ### Manually
358 |
359 | If you prefer not to use either of the aforementioned dependency managers, you can integrate AttributedTextView into your project manually.
360 |
361 | #### Git Submodules
362 |
363 | - Open up Terminal, `cd` into your top-level project directory, and run the following command "if" your project is not initialized as a git repository:
364 |
365 | ```bash
366 | $ git init
367 | ```
368 |
369 | - Add AttributedTextView as a git [submodule](http://git-scm.com/docs/git-submodule) by running the following command:
370 |
371 | ```bash
372 | $ git submodule add https://github.com/evermeer/AttributedTextView.git
373 | $ git submodule update --init --recursive
374 | ```
375 |
376 | - Open the new `AttributedTextView` folder, and drag the `AttributedTextView.xcodeproj` into the Project Navigator of your application's Xcode project.
377 |
378 | > It should appear nested underneath your application's blue project icon. Whether it is above or below all the other Xcode groups does not matter.
379 |
380 | - Select the `AttributedTextView.xcodeproj` in the Project Navigator and verify the deployment target matches that of your application target.
381 | - Next, select your application project in the Project Navigator (blue project icon) to navigate to the target configuration window and select the application target under the "Targets" heading in the sidebar.
382 | - In the tab bar at the top of that window, open the "General" panel.
383 | - Click on the `+` button under the "Embedded Binaries" section.
384 | - You will see two different `AttributedTextView.xcodeproj` folders each with two different versions of the `AttributedTextView.framework` nested inside a `Products` folder.
385 |
386 | > It does not matter which `Products` folder you choose from.
387 |
388 | - Select the `AttributedTextView.framework`.
389 |
390 | - And that's it!
391 |
392 | > The `AttributedTextView.framework` is automagically added as a target dependency, linked framework and embedded framework in a copy files build phase which is all you need to build on the simulator and a device.
393 |
394 | #### Embeded Binaries
395 |
396 | - Download the latest release from https://github.com/evermeer/AttributedTextView/releases
397 | - Next, select your application project in the Project Navigator (blue project icon) to navigate to the target configuration window and select the application target under the "Targets" heading in the sidebar.
398 | - In the tab bar at the top of that window, open the "General" panel.
399 | - Click on the `+` button under the "Embedded Binaries" section.
400 | - Add the downloaded `AttributedTextView.framework`.
401 | - And that's it!
402 |
403 | ## License
404 |
405 | AttributedTextView is released under the MIT license. See [LICENSE](https://github.com/evermeer/AttributedTextView/blob/master/LICENSE) for details.
406 |
407 | ## My other libraries:
408 | Also see my other public source iOS libraries:
409 |
410 | - [EVReflection](https://github.com/evermeer/EVReflection) - Reflection based (Dictionary, CKRecord, JSON and XML) object mapping with extensions for Alamofire and Moya with RxSwift or ReactiveSwift
411 | - [EVCloudKitDao](https://github.com/evermeer/EVCloudKitDao) - Simplified access to Apple's CloudKit
412 | - [EVFaceTracker](https://github.com/evermeer/EVFaceTracker) - Calculate the distance and angle of your device with regards to your face in order to simulate a 3D effect
413 | - [EVURLCache](https://github.com/evermeer/EVURLCache) - a NSURLCache subclass for handling all web requests that use NSURLReques
414 | - [AlamofireOauth2](https://github.com/evermeer/AlamofireOauth2) - A swift implementation of OAuth2 using Alamofire
415 | - [EVWordPressAPI](https://github.com/evermeer/EVWordPressAPI) - Swift Implementation of the WordPress (Jetpack) API using AlamofireOauth2, AlomofireJsonToObjects and EVReflection (work in progress)
416 | - [PassportScanner](https://github.com/evermeer/PassportScanner) - Scan the MRZ code of a passport and extract the firstname, lastname, passport number, nationality, date of birth, expiration date and personal numer.
417 | - [AttributedTextView](https://github.com/evermeer/AttributedTextView) - Easiest way to create an attributed UITextView with support for multiple links (url, hashtags, mentions).
418 |
419 |
420 |
--------------------------------------------------------------------------------
/Screenshots/IconList.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evermeer/AttributedTextView/c937141724bb797ea29ea4b7055fa78f10911f36/Screenshots/IconList.png
--------------------------------------------------------------------------------
/Screenshots/Sample1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evermeer/AttributedTextView/c937141724bb797ea29ea4b7055fa78f10911f36/Screenshots/Sample1.png
--------------------------------------------------------------------------------
/Screenshots/Sample2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evermeer/AttributedTextView/c937141724bb797ea29ea4b7055fa78f10911f36/Screenshots/Sample2.png
--------------------------------------------------------------------------------
/Screenshots/Sample3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evermeer/AttributedTextView/c937141724bb797ea29ea4b7055fa78f10911f36/Screenshots/Sample3.png
--------------------------------------------------------------------------------
/Screenshots/Sample4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evermeer/AttributedTextView/c937141724bb797ea29ea4b7055fa78f10911f36/Screenshots/Sample4.png
--------------------------------------------------------------------------------
/Screenshots/Sample5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evermeer/AttributedTextView/c937141724bb797ea29ea4b7055fa78f10911f36/Screenshots/Sample5.png
--------------------------------------------------------------------------------
/Screenshots/Sample6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/evermeer/AttributedTextView/c937141724bb797ea29ea4b7055fa78f10911f36/Screenshots/Sample6.png
--------------------------------------------------------------------------------
/Sources/AttributedLabel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AttributedLabel.swift
3 | // AttributedTextView
4 | //
5 | // Created by Edwin Vermeer on 03/03/2017.
6 | // Copyright © 2017 evermeer. All rights reserved.
7 | //
8 | import UIKit
9 |
10 | /**
11 | Create your own lable class and use this class as it's base class. override the configureAttributedLabel function and set the self.attributedText to your prefered styling. For instance self.attributedText = self.text?.myHeader.attributedText See the samples for how you could add your own custom property for interface builder and alsu use that.
12 | */
13 | @IBDesignable open class AttributedLabel: UILabel {
14 | override public init(frame: CGRect) {
15 | super.init(frame: frame)
16 | }
17 |
18 | required public init?(coder aDecoder: NSCoder) {
19 | super.init(coder: aDecoder)
20 | }
21 |
22 | open override func awakeFromNib() {
23 | super.awakeFromNib()
24 | configureAttributedLabel()
25 | }
26 |
27 | open override func prepareForInterfaceBuilder() {
28 | super.prepareForInterfaceBuilder()
29 | configureAttributedLabel()
30 | }
31 |
32 | override open var text: String? {
33 | didSet {
34 | configureAttributedLabel()
35 | }
36 | }
37 |
38 | open func configureAttributedLabel() {
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Sources/AttributedTextView.h:
--------------------------------------------------------------------------------
1 | //
2 | // AttributedTextView.h
3 | // AttributedTextView
4 | //
5 | // Created by Edwin Vermeer on 04/10/16.
6 | // Copyright © 2016 evermeer. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for AttributedTextView.
12 | FOUNDATION_EXPORT double AttributedTextViewVersionNumber;
13 |
14 | //! Project version string for AttributedTextView.
15 | FOUNDATION_EXPORT const unsigned char AttributedTextViewVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
18 |
--------------------------------------------------------------------------------
/Sources/AttributedTextView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftyLabel.swift
3 | //
4 | // Created by Edwin Vermeer on 25/11/2016.
5 | // Copyright © 2016 Edwin Vermeer. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | /**
11 | Set this class as the 'Custom Class' when you add a UITextView in the interfacebuilder.
12 | Use the attributer property for setting the attributed text.
13 |
14 | You can create your own textview class and use this class as it's base class. override the configureAttributedLabel function and set the self.attributer to your prefered styling. For instance self.attributer = self.text?.myHeader See the samples for how you could add your own custom property for interface builder and alsu use that.
15 | */
16 | @IBDesignable open class AttributedTextView: UITextView, UITextViewDelegate {
17 |
18 | // required when using @IBDesignable
19 | override public init(frame: CGRect, textContainer: NSTextContainer?) {
20 | super.init(frame: frame, textContainer: textContainer)
21 | super.delegate = self
22 | }
23 |
24 | // required when using @IBDesignable
25 | required public init?(coder aDecoder: NSCoder) {
26 | super.init(coder: aDecoder)
27 | super.delegate = self
28 | }
29 |
30 | // Make sure configureAttributedTextView is called right after activation from the storyboard.
31 | open override func awakeFromNib() {
32 | super.awakeFromNib()
33 | configureAttributedTextView()
34 | }
35 |
36 | // Make sure configureAttributedTextView is called inside interfacebuilder
37 | open override func prepareForInterfaceBuilder() {
38 | super.prepareForInterfaceBuilder()
39 | configureAttributedTextView()
40 | }
41 |
42 | // just an override for triggering configureAttributedTextView
43 | override open var text: String? {
44 | didSet {
45 | configureAttributedTextView()
46 | }
47 | }
48 |
49 | // For enabeling the size adjustment
50 | @IBInspectable open var forceIntrinsicContentSizeToBeContentSize: Bool = false {
51 | didSet { configureAttributedTextView() }
52 | }
53 |
54 | // Return the contentSize if its forced enabled
55 | override open var intrinsicContentSize: CGSize {
56 | return self.forceIntrinsicContentSizeToBeContentSize ? self.contentSize : super.intrinsicContentSize
57 | }
58 |
59 | // Subclass AttributedTextView and override this function if you want to use easy custum controls in interface builder
60 | open func configureAttributedTextView() {
61 | }
62 |
63 | // storage variable for the Attributer
64 | private var _attributer: Attributer?
65 |
66 | /**
67 | The attributer object that will set the attributedText
68 | */
69 | open var attributer: Attributer {
70 | get {
71 | if _attributer == nil {
72 | _attributer = Attributer("")
73 | }
74 | return _attributer!
75 | }
76 | set {
77 | _attributer = newValue
78 | self.attributedText = _attributer?.attributedText
79 |
80 | if _attributer?.hasCallbacks() ?? false {
81 | // Without these makeInteract does not work
82 | self.isUserInteractionEnabled = true
83 | self.isSelectable = true
84 | self.isEditable = false
85 | }
86 | if let color = _attributer?.linkColor {
87 | self.linkTextAttributes = [NSAttributedString.Key.foregroundColor: color as Any]
88 | }
89 | }
90 | }
91 |
92 | /**
93 | If you manually set the delegate on the AttributedTextView, then it will set this property instead of the actual delegate. The actual delegate will be set to this class itself for handling the interactions on the links. events will be forwarded to the _delegate.
94 | */
95 | public var _delegate: UITextViewDelegate?
96 | /**
97 | Delegate that can be set for forwarding events from the UITextView
98 | */
99 | override open var delegate: UITextViewDelegate? {
100 | get {
101 | return super.delegate
102 | }
103 | set {
104 | _delegate = newValue
105 | }
106 | }
107 |
108 |
109 | // MARK: - UITextViewDelegate functions - forwarding all to _delegate
110 |
111 | /**
112 | UITextViewDelegate function for forwarding the textViewShoudlBeginEditing
113 |
114 | -property textView: The UITextView where the delegate is called on
115 | */
116 | public func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
117 | return _delegate?.textViewShouldBeginEditing?(textView) ?? self.isEditable
118 | }
119 |
120 | /**
121 | UITextViewDelegate function for forwarding the textViewShouldEndEditing
122 |
123 | -property textView: The UITextView where the delegate is called on
124 | */
125 | public func textViewShouldEndEditing(_ textView: UITextView) -> Bool {
126 | return _delegate?.textViewShouldEndEditing?(textView) ?? self.isEditable
127 | }
128 |
129 | /**
130 | UITextViewDelegate function for forwarding the textViewDidBeginEditing
131 |
132 | -property textView: The UITextView where the delegate is called on
133 | */
134 | public func textViewDidBeginEditing(_ textView: UITextView) {
135 | _delegate?.textViewDidBeginEditing?(textView)
136 | }
137 |
138 | /**
139 | UITextViewDelegate function for forwarding the textViewDidEndEditing
140 |
141 | -property textView: The UITextView where the delegate is called on
142 | */
143 | public func textViewDidEndEditing(_ textView: UITextView) {
144 | _delegate?.textViewDidEndEditing?(textView)
145 | }
146 |
147 |
148 | /**
149 | UITextViewDelegate function for forwarding the shouldChangeTextIn range
150 |
151 | -property textView: The UITextView where the delegate is called on
152 | -property shouldChangeTextIn: the range
153 | -property replacementText: the replacement text
154 | */
155 | public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
156 | return _delegate?.textView?(textView, shouldChangeTextIn: range, replacementText: text) ?? self.isEditable
157 | }
158 |
159 | /**
160 | UITextViewDelegate function for forwarding the textViewDidChange
161 |
162 | -property textView: The UITextView where the delegate is called on
163 | */
164 | public func textViewDidChange(_ textView: UITextView) {
165 | _delegate?.textViewDidChange?(textView)
166 | }
167 |
168 | /**
169 | UITextViewDelegate function for forwarding the textViewDidChangeSelection
170 |
171 | -property textView: The UITextView where the delegate is called on
172 | */
173 | public func textViewDidChangeSelection(_ textView: UITextView) {
174 | _delegate?.textViewDidChangeSelection?(textView)
175 | }
176 |
177 | /**
178 | UITextViewDelegate function for forwarding the shouldInteractWith URL
179 |
180 | -property textView: The UITextView where the delegate is called on
181 | -property shouldInteractWith: The URL to interact with
182 | -property characterRagne: the NSRange for the selection
183 | -property interaction: The UITextItemInteraction
184 | */
185 | @available(iOS 10.0, *)
186 | public func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
187 | _attributer?.interactWithURL(URL: URL)
188 | return _delegate?.textView?(textView, shouldInteractWith: URL, in: characterRange, interaction: interaction) ?? false
189 | }
190 |
191 | /**
192 | UITextViewDelegate function for forwarding the shouldInteractWith textAttachment
193 |
194 | -property textView: The UITextView where the delegate is called on
195 | -property shouldInteractWith: the NSTextAttachement
196 | -property characterRange: the NSRange for the selection
197 | -property interaction: The UITextItemInteraction
198 | */
199 | @available(iOS 10.0, *)
200 | public func textView(_ textView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
201 | return _delegate?.textView?(textView, shouldInteractWith: textAttachment, in: characterRange, interaction: interaction) ?? false
202 | }
203 |
204 | /**
205 | UITextViewDelegate function for forwarding the shouldInteractWith URL
206 |
207 | -property textView: The UITextView where the delegate is called on
208 | -property shouldInteractWith: the NSTextAttachement
209 | -property characterRange: the NSRange for the selection
210 | */
211 | @available(iOS, introduced: 7.0, deprecated: 10.0, message: "Use textView:shouldInteractWithURL:inRange:forInteractionType: instead")
212 | public func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
213 | _attributer?.interactWithURL(URL: URL)
214 | return _delegate?.textView!(textView, shouldInteractWith: URL, in: characterRange) ?? false
215 | }
216 |
217 | /**
218 | UITextViewDelegate function for forwarding the shouldInteractWith textAttachment
219 |
220 | -property textView: The UITextView where the delegate is called on
221 | -property shouldInteractWith: the NSTextAttachement
222 | -property characterRange: the NSRange for the selection
223 | */
224 | @available(iOS, introduced: 7.0, deprecated: 10.0, message: "Use textView:shouldInteractWithTextAttachment:inRange:forInteractionType: instead")
225 | public func textView(_ textView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange) -> Bool {
226 | return _delegate?.textView?(textView, shouldInteractWith: textAttachment, in: characterRange) ?? false
227 | }
228 | }
229 |
230 |
231 |
--------------------------------------------------------------------------------
/Sources/Attributer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Attributer.swift
3 | //
4 | // Created by Edwin Vermeer on 25/11/2016.
5 | // Copyright © 2016 Edwin Vermeer. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | /**
11 | The core of working with attributed strings
12 | */
13 | open class Attributer {
14 |
15 |
16 | /**
17 | You can get the final NSMutableAttributedString from here
18 | */
19 |
20 | open var attributedText: NSMutableAttributedString
21 | /**
22 | Used to set the link color on the UITextView
23 | */
24 | public var linkColor: UIColor?
25 |
26 | /**
27 | Save all the callbacks (from the .makeInteract)
28 | */
29 | fileprivate var urlCallbacks: [String : ((_ link: String) -> ())] = [:]
30 |
31 | /**
32 | Do we have interactions
33 | */
34 | public func hasCallbacks() -> Bool {
35 | return urlCallbacks.count > 0
36 | }
37 |
38 | /**
39 | The current active ranges that will be influenced by all functions.
40 | */
41 | fileprivate var ranges: [NSRange] = [] {
42 | didSet {
43 | paragraphStyle = NSMutableParagraphStyle()
44 | }
45 | }
46 |
47 | /**
48 | The current active paragraphStyle
49 | */
50 | fileprivate var paragraphStyle = NSMutableParagraphStyle()
51 |
52 |
53 | /**
54 | Contructor method for Attributer
55 |
56 | Instantiate a new Attributer based on an attributed string
57 |
58 | -parameter string: The NSMutableAttributedString that will be used as the initial value of Attributer.
59 | */
60 | public init(_ string: NSMutableAttributedString) {
61 | self.attributedText = string
62 | ranges.append(NSRange(location: 0, length: self.attributedText.length))
63 | }
64 |
65 | /**
66 | Contructor method for Attributer
67 |
68 | Instantiate a new Attributer based on an attributed string (converted to a mutable first)
69 |
70 | -parameter string: The NSAttributedString that will be used as the initial value of Attributer.
71 | */
72 | public convenience init(_ string: NSAttributedString) {
73 | self.init(NSMutableAttributedString(attributedString: string))
74 | }
75 |
76 | /**
77 | Contructor method for Attributer
78 |
79 | Instantiate a new Attributer based on a string
80 |
81 | -parameter string: The NSString that will be used as the initial value of Attributer.
82 | */
83 | public convenience init(_ string: NSString) {
84 | self.init(NSMutableAttributedString(string: string as String))
85 | }
86 |
87 | /**
88 | Contructor method for Attributer
89 |
90 | Instantiate a new Attributer based on a string
91 |
92 | -parameter string: The String that will be used as the initial value of Attributer.
93 | */
94 | public convenience init(_ string: String) {
95 | self.init(NSMutableAttributedString(string: string))
96 | }
97 |
98 | /**
99 | Contructor method for Attributer
100 |
101 | Instantiate a new Attributer based on a string
102 |
103 | -parameter string: The String that will be used as the initial value of Attributer.
104 | */
105 | public convenience init(_ image: UIImage, bounds: CGRect? = nil) {
106 | let imageAttachment = NSTextAttachment()
107 | imageAttachment.image = image
108 | if let bounds = bounds {
109 | imageAttachment.bounds = bounds
110 | }
111 | self.init(NSAttributedString(attachment: imageAttachment))
112 | }
113 |
114 |
115 | //MARK - Color functions
116 |
117 |
118 | /**
119 | Apply the color black to the active range(es)
120 | */
121 | open var black: Attributer {
122 | get {
123 | return applyAttributes(NSAttributedString.Key.foregroundColor.rawValue, value: UIColor.black)
124 | }
125 | }
126 |
127 | /**
128 | Apply the color darkGray to the active range(es)
129 | */
130 | open var darkGray: Attributer {
131 | get {
132 | return applyAttributes(NSAttributedString.Key.foregroundColor.rawValue, value: UIColor.darkGray)
133 | }
134 | }
135 |
136 | /**
137 | Apply the color lightGray to the active range(es)
138 | */
139 | open var lightGray: Attributer {
140 | get {
141 | return applyAttributes(NSAttributedString.Key.foregroundColor.rawValue, value: UIColor.lightGray)
142 | }
143 | }
144 |
145 | /**
146 | Apply the color white to the active range(es)
147 | */
148 | open var white: Attributer {
149 | get {
150 | return applyAttributes(NSAttributedString.Key.foregroundColor.rawValue, value: UIColor.white)
151 | }
152 | }
153 |
154 | /**
155 | Apply the color gray to the active range(es)
156 | */
157 | open var gray: Attributer {
158 | get {
159 | return applyAttributes(NSAttributedString.Key.foregroundColor.rawValue, value: UIColor.gray)
160 | }
161 | }
162 |
163 | /**
164 | Apply the color red to the active range(es)
165 | */
166 | open var red: Attributer {
167 | get {
168 | return applyAttributes(NSAttributedString.Key.foregroundColor.rawValue, value: UIColor.red)
169 | }
170 | }
171 |
172 | /**
173 | Apply the color green to the active range(es)
174 | */
175 | open var green: Attributer {
176 | get {
177 | return applyAttributes(NSAttributedString.Key.foregroundColor.rawValue, value: UIColor.green)
178 | }
179 | }
180 |
181 | /**
182 | Apply the color blue to the active range(es)
183 | */
184 | open var blue: Attributer {
185 | get {
186 | return applyAttributes(NSAttributedString.Key.foregroundColor.rawValue, value: UIColor.blue)
187 | }
188 | }
189 |
190 | /**
191 | Apply the color cyan to the active range(es)
192 | */
193 | open var cyan: Attributer {
194 | get {
195 | return applyAttributes(NSAttributedString.Key.foregroundColor.rawValue, value: UIColor.cyan)
196 | }
197 | }
198 |
199 | /**
200 | Apply the color yellow to the active range(es)
201 | */
202 | open var yellow: Attributer {
203 | get {
204 | return applyAttributes(NSAttributedString.Key.foregroundColor.rawValue, value: UIColor.yellow)
205 | }
206 | }
207 |
208 | /**
209 | Apply the color magenta to the active range(es)
210 | */
211 | open var magenta: Attributer {
212 | get {
213 | return applyAttributes(NSAttributedString.Key.foregroundColor.rawValue, value: UIColor.magenta)
214 | }
215 | }
216 |
217 | /**
218 | Apply the color orange to the active range(es)
219 | */
220 | open var orange: Attributer {
221 | get {
222 | return applyAttributes(NSAttributedString.Key.foregroundColor.rawValue, value: UIColor.orange)
223 | }
224 | }
225 |
226 | /**
227 | Apply the color purple to the active range(es)
228 | */
229 | open var purple: Attributer {
230 | get {
231 | return applyAttributes(NSAttributedString.Key.foregroundColor.rawValue, value: UIColor.purple)
232 | }
233 | }
234 |
235 | /**
236 | Apply the color brown to the active range(es)
237 | */
238 | open var brown: Attributer {
239 | get {
240 | return applyAttributes(NSAttributedString.Key.foregroundColor.rawValue, value: UIColor.brown)
241 | }
242 | }
243 |
244 | /**
245 | Apply the color clear to the active range(es)
246 | */
247 | open var clear: Attributer {
248 | get {
249 | return applyAttributes(NSAttributedString.Key.foregroundColor.rawValue, value: UIColor.clear)
250 | }
251 | }
252 |
253 | /**
254 | Apply a UIColor to the active range(es)
255 |
256 | -parameter color: The UIColor that will be applied.
257 | */
258 | open func color(_ color: UIColor) -> Attributer {
259 | return applyAttributes(NSAttributedString.Key.foregroundColor.rawValue, value: color)
260 | }
261 |
262 | /**
263 | Apply a color from a hex value to the active range(es)
264 |
265 | -parameter hex: The hex value of the color that will be applied.
266 | */
267 | open func color(_ hex: Int) -> Attributer {
268 | return applyAttributes(NSAttributedString.Key.foregroundColor.rawValue, value: colorFrom(hex: hex))
269 | }
270 |
271 | // MARK - Selection functions
272 |
273 |
274 | /**
275 | Make the active range the entire text
276 | */
277 | open var all: Attributer {
278 | get {
279 | ranges.removeAll()
280 | ranges.append(NSRange(location: 0, length: self.attributedText.length))
281 | return self
282 | }
283 | }
284 |
285 | /**
286 | Make the active range (from and to)
287 |
288 | -parameter from: The start of the new range
289 | -parameter to: The end of the new range.
290 | */
291 | open func range(_ from: Int, to: Int) -> Attributer {
292 | ranges.removeAll()
293 | ranges.append(NSRange(location: from, length: to - from))
294 | return self
295 | }
296 |
297 | /**
298 | Make the active range (location and length)
299 |
300 | -parameter location: The location of the new range
301 | -parameter length: The length of the new range.
302 | */
303 | open func range(_ location: Int, length: Int) -> Attributer {
304 | ranges.removeAll()
305 | ranges.append(NSRange(location: location, length: length))
306 | return self
307 | }
308 |
309 |
310 | /**
311 | Just set the active range
312 |
313 | -parameter range: The new range.
314 | */
315 | open func range(_ range: NSRange) -> Attributer {
316 | ranges.removeAll()
317 | ranges.append(range)
318 | return self
319 | }
320 |
321 | /**
322 | Find the first occurrance of a string (search with using .CompareOptions)
323 |
324 | -parameter substring: The string to search for.
325 | -parameter options: The search options
326 | */
327 | open func matchWithOptions(_ substring: String, _ options: NSString.CompareOptions = .literal) -> Attributer {
328 | let string = self.attributedText.string as NSString
329 | ranges.removeAll()
330 | ranges.append(string.range(of: substring, options: options))
331 | return self
332 | }
333 |
334 | /**
335 | Find the first occurrance of a string
336 |
337 | -parameter substring: The string to search for.
338 | */
339 | open func match(_ substring: String) -> Attributer {
340 | return matchWithOptions(substring)
341 | }
342 |
343 | /**
344 | Find the all occurrances of a string (search with using .CompareOptions)
345 |
346 | -parameter substring: The string to search for.
347 | -parameter options: The search options
348 | */
349 | open func matchAllWithOptions(_ substring: String, _ options: NSString.CompareOptions = .literal) -> Attributer {
350 | let string = self.attributedText.string as NSString
351 | var range = string.range(of: substring, options: options)
352 | ranges.removeAll()
353 | ranges.append(range)
354 | while range.length != 0 {
355 | let location = range.location + range.length
356 | let length = string.length - location
357 | range = string.range(of: substring, options: options, range: NSRange(location: location, length: length))
358 | ranges.append(range)
359 | }
360 | return self
361 | }
362 |
363 | /**
364 | Find the all occurrances of a string
365 |
366 | -parameter substring: The string to search for.
367 | */
368 | open func matchAll(_ substring: String) -> Attributer {
369 | return matchAllWithOptions(substring)
370 | }
371 |
372 | /**
373 | Find the all occurrances of any of the strings (search with using .CompareOptions)
374 |
375 | -parameter substring: An array of string to search for.
376 | -parameter options: The search options
377 | */
378 | open func matchAnyWithOptions(_ substrings: [String], _ options: NSString.CompareOptions = .literal) -> Attributer {
379 | let string = self.attributedText.string as NSString
380 | ranges.removeAll()
381 | for substring in substrings {
382 | var range = string.range(of: substring, options: options)
383 | ranges.append(range)
384 | while range.length != 0 {
385 | let location = range.location + range.length
386 | let length = string.length - location
387 | range = string.range(of: substring, options: options, range: NSRange(location: location, length: length))
388 | ranges.append(range)
389 | }
390 | }
391 | return self
392 | }
393 |
394 | /**
395 | Find the all occurrances of any of the strings
396 |
397 | -parameter substring: The array of strings to search for.
398 | */
399 | open func matchAny(_ substrings: [String]) -> Attributer {
400 | return matchAnyWithOptions(substrings)
401 | }
402 |
403 | /**
404 | Find the all hashtags (words beginning with #)
405 | */
406 | open var matchHashtags: Attributer {
407 | get {
408 | return matchPattern("#[\\p{L}0-9_]*")
409 | }
410 | }
411 |
412 | /**
413 | Find the all mentions (words beginning with @)
414 | */
415 | open var matchMentions: Attributer {
416 | get {
417 | return matchPattern("@[\\p{L}0-9_]*")
418 | }
419 | }
420 |
421 | /**
422 | Find the all links
423 | */
424 | open var matchLinks: Attributer {
425 | get {
426 | return matchPattern("(([\\w]+:)?//)?(([\\d\\w]|%[a-fA-f\\d]{2,2})+(:([\\d\\w]|%[a-fA-f\\d]{2,2})+)?@)?([\\d\\w][-\\d\\w]{0,253}[\\d\\w]\\.)+[\\w]{2,63}(:[\\d]+)?(/([-+_~.\\d\\w]|%[a-fA-f\\d]{2,2})*)*(\\?(&?([-+_~.\\d\\w]|%[a-fA-f\\d]{2,2})=?)*)?(#([-+_~.\\d\\w]|%[a-fA-f\\d]{2,2})*)?")
427 | }
428 | }
429 |
430 | /**
431 | Use a regular expression patter to find ranges
432 |
433 | -parameter pattern: The regex pattern.
434 | */
435 | open func matchPattern(_ pattern: String) -> Attributer {
436 | guard let elementRegex: NSRegularExpression = Attributer.regularExpression(for: pattern) else { return self }
437 | let range = NSRange(location: 0, length: (self.attributedText.string as NSString).length) // .characters.count is
438 | let results: [NSTextCheckingResult] = elementRegex.matches(in: self.attributedText.string, options: [], range: range)
439 | ranges = results.map { $0.range }
440 | return self
441 | }
442 |
443 | /**
444 | Append a string to the current attributed text and select the new string as the active range
445 |
446 | -parameter string: The string that will be added
447 | */
448 | open func append(_ string: String) -> Attributer {
449 | return self.append(NSMutableAttributedString(string: string))
450 | }
451 |
452 | /**
453 | Append a string to the current attributed text and select the new string as the active range
454 |
455 | -parameter string: The string that will be added
456 | */
457 | open func appendHtml(_ string: String) -> Attributer {
458 | return self.append(NSMutableAttributedString(html: string) ?? NSMutableAttributedString())
459 | }
460 |
461 | /**
462 | Append an attributed string to the current attributed text and select the new string as the active range
463 |
464 | -parameter attributedString: The attributed string that will be added
465 | */
466 | open func append(_ attributedString: NSMutableAttributedString) -> Attributer {
467 | ranges.removeAll()
468 | let rememberLength = self.attributedText.length
469 | self.attributedText.append(attributedString)
470 | ranges.append(NSRange(location: rememberLength, length: attributedString.length))
471 | return self
472 | }
473 |
474 | /**
475 | Append an Attributer to the current and select the new string as the active range
476 |
477 | -parameter attributer: The Attributer that will be added
478 | */
479 | open func append(_ attributer: Attributer) -> Attributer {
480 | for callback in attributer.urlCallbacks {
481 | self.urlCallbacks[callback.key] = callback.value
482 | }
483 | self.linkColor = attributer.linkColor ?? self.linkColor
484 | return self.append(attributer.attributedText)
485 |
486 | }
487 |
488 |
489 |
490 | // MARK: - Font
491 |
492 | /**
493 | Apply a font to the active range
494 |
495 | -parameter fontName: The name of the font that will be applied
496 | */
497 | open func fontName(_ fontName: String) -> Attributer {
498 | for range in self.ranges {
499 | guard range.location != NSNotFound else { return self }
500 | let substring = self.attributedText.attributedSubstring(from: range)
501 | if substring.length > 0, let font = substring.attribute(NSAttributedString.Key.font, at: 0, effectiveRange:nil) as? UIFont {
502 | if let currentFont = UIFont(name: fontName, size: font.pointSize) {
503 | self.attributedText.addAttribute(NSAttributedString.Key.font, value: currentFont, range: range)
504 | }
505 | } else {
506 | if let currentFont = UIFont(name: fontName, size: UIFont.systemFontSize) {
507 | self.attributedText.addAttribute(NSAttributedString.Key.font, value: currentFont, range: range)
508 | }
509 | }
510 | }
511 | return self
512 | }
513 |
514 | /**
515 | Apply a fontsize to the active range
516 |
517 | -parameter size: The fontsize that will be applied
518 | */
519 | open func size(_ size: CGFloat) -> Attributer {
520 | for range in self.ranges {
521 | guard range.location != NSNotFound else { return self }
522 | let substring = self.attributedText.attributedSubstring(from: range)
523 | if substring.length > 0, let font = substring.attribute(NSAttributedString.Key.font, at: 0, effectiveRange:nil) as? UIFont {
524 | self.attributedText.addAttribute(NSAttributedString.Key.font, value: UIFont(name: font.fontName, size: size)!, range: range)
525 | } else {
526 | self.attributedText.addAttribute(NSAttributedString.Key.font, value: UIFont.systemFont(ofSize: size), range: range)
527 | }
528 | }
529 | return self
530 | }
531 |
532 | /**
533 | Apply a font to the active range
534 |
535 | -parameter font: The UIFont that will be applied
536 | */
537 | open func font(_ font: UIFont?) -> Attributer {
538 | if font != nil {
539 | return fontName(font!.fontName).size(font!.pointSize)
540 | }
541 | return self
542 | }
543 |
544 |
545 | // MARK: - Create a link
546 |
547 | /**
548 | Make the current active range interact. A touch will call the UITextViewDelegate which is handled in the AttributedTextView
549 |
550 | -parameter scheme: The scheme that will be added in front of the link
551 | */
552 | @available(*, deprecated, message: "use AttributedTextView with makeInteract: instead")
553 | open func makeInteractWithURLforScheme(_ scheme: String) -> Attributer {
554 | for nsRange in self.ranges {
555 | guard nsRange.location != NSNotFound else { return self }
556 | let iRange = self.attributedText.string.range(from: nsRange)
557 | if let escapedString = self.attributedText.string.substring(with: iRange!).addingPercentEncoding(withAllowedCharacters: NSCharacterSet.urlHostAllowed) {
558 | self.attributedText.addAttribute(NSAttributedString.Key.link, value: "\(scheme):\(escapedString)", range: nsRange)
559 | }
560 | }
561 | return self
562 | }
563 |
564 | /**
565 | Make the current active range interact. A touch will call the UITextViewDelegate. When using the AttributedTextView the callback function will be called
566 |
567 | -parameter callback: The callback function that will be called when using AttributedTextView
568 | */
569 | open func makeInteract(_ callback: @escaping ((_ link: String) -> ())) -> Attributer {
570 | for nsRange in self.ranges {
571 | guard nsRange.location != NSNotFound else { return self }
572 | let unEscapedString = (self.attributedText.string as NSString).substring(with: nsRange)
573 | let escapedString = unEscapedString.addingPercentEncoding(withAllowedCharacters: NSCharacterSet.urlHostAllowed) ?? ""
574 | self.attributedText.addAttribute(NSAttributedString.Key.link, value: "AttributedTextView:\(escapedString)", range: nsRange)
575 | urlCallbacks[unEscapedString] = callback
576 |
577 | }
578 | return self
579 | }
580 |
581 | /**
582 | Used for setting the link color of the UITextView
583 |
584 | -parameter color: The color of all links
585 | */
586 | open func setLinkColor(_ color: UIColor) -> Attributer {
587 | linkColor = color
588 | return self
589 | }
590 |
591 | /**
592 | Called by AttributedTextView when a URL is touched
593 |
594 | -parameter URL: The URL that was touched
595 | */
596 | public func interactWithURL(URL: URL) {
597 | let unescapedString = URL.absoluteString.replacingOccurrences(of: "AttributedTextView:", with: "").removingPercentEncoding ?? ""
598 | urlCallbacks[unescapedString]?(unescapedString)
599 | }
600 |
601 | /**
602 | Make the current selected range a link
603 |
604 | -parameter link: The URL that will be forwarded to the UITextViewDelegate
605 | */
606 | open func link(_ link: URL) -> Attributer {
607 | return applyAttributes(NSAttributedString.Key.link.rawValue, value: link as AnyObject)
608 | }
609 |
610 | /**
611 | Make the current selected range a link
612 |
613 | -parameter link: The NSString that will be forwarded to the UITextViewDelegate
614 | */
615 | open func link(_ link: NSString) -> Attributer {
616 | return applyAttributes(NSAttributedString.Key.link.rawValue, value: link)
617 | }
618 |
619 | // MARK: - Style
620 |
621 | /**
622 | underline the active range
623 | */
624 | open var underline: Attributer {
625 | return underline(NSUnderlineStyle.single)
626 | }
627 |
628 | /**
629 | set the underline style for the active range (you also have to call .underline)
630 |
631 | -parameter underline: The underline style
632 | */
633 | open func underline(_ style: NSUnderlineStyle) -> Attributer {
634 | return applyAttributes(NSAttributedString.Key.underlineStyle.rawValue, value: style.rawValue as AnyObject)
635 | }
636 |
637 | /**
638 | set the underline style for the active range (you also have to call .underline)
639 |
640 | -parameter style: The underline style
641 | -parameter pattern : The underline pattern
642 | */
643 | open func underline(_ style: NSUnderlineStyle, _ pattern: NSUnderlineStyle) -> Attributer {
644 | return applyAttributes(NSAttributedString.Key.underlineStyle.rawValue, value: NSNumber(value: (style.rawValue | pattern.rawValue)))
645 | }
646 |
647 | /**
648 | set the underline color for the active range (you also have to call .underline)
649 |
650 | -parameter color: the UIColor of the undeline
651 | */
652 | open func underline(_ color: UIColor) -> Attributer {
653 | return applyAttributes(NSAttributedString.Key.underlineColor.rawValue, value: color)
654 | }
655 |
656 | /**
657 | set the underline color for the active range (you also have to call .underline)
658 |
659 | -parameter hex: the hex value of the color for the underline
660 | */
661 | open func underline(_ hex: Int) -> Attributer {
662 | return applyAttributes(NSAttributedString.Key.underlineColor.rawValue, value: colorFrom(hex: hex))
663 | }
664 |
665 | /**
666 | Attach an image to the range
667 |
668 | -parameter image: the UIImage that will be used as the attachment
669 | */
670 | open func attach(_ image: UIImage?, bounds: CGRect? = nil) -> Attributer {
671 | let imageAttachment = NSTextAttachment()
672 | imageAttachment.image = image
673 | if let bounds = bounds {
674 | imageAttachment.bounds = bounds
675 | }
676 | self.attributedText.append(NSAttributedString(attachment: imageAttachment))
677 | return self
678 | }
679 |
680 | /**
681 | Attach an image to the range
682 |
683 | -parameter imageStr: the name of the image that will be used as the attachment
684 | */
685 | open func attach(_ imageStr: String, bounds: CGRect? = nil) -> Attributer {
686 | let imageAttachment = NSTextAttachment()
687 | imageAttachment.image = UIImage(named: imageStr)
688 | if let bounds = bounds {
689 | imageAttachment.bounds = bounds
690 | }
691 | self.attributedText.append(NSAttributedString(attachment: imageAttachment))
692 | return self
693 | }
694 |
695 | /**
696 | Set the shadow for the active range(es)
697 |
698 | -parameter shadow: The NSShadow that will be set
699 | */
700 | open func shadow(_ shadow: NSShadow) -> Attributer {
701 | return applyAttributes(NSAttributedString.Key.shadow.rawValue, value: shadow)
702 | }
703 |
704 | /**
705 | Set the shadow for the active range(es)
706 |
707 | -parameter color: The UIColor for the shadow
708 | -parameter offset: The CGSize offset for the shadow
709 | -parameter blurRadius: The blurRadius for the shadow
710 | */
711 | open func shadow(color: UIColor?, offset: CGSize, blurRadius: CGFloat) -> Attributer {
712 | let shadow = NSShadow()
713 | shadow.shadowColor = color
714 | shadow.shadowOffset = offset
715 | shadow.shadowBlurRadius = blurRadius
716 | return applyAttributes(NSAttributedString.Key.shadow.rawValue, value: shadow)
717 | }
718 |
719 | /**
720 | Set the baseline offset for the active range(es)
721 | This will move the text up or down
722 |
723 | -parameter offset: The number of pixels that the text will be moved up or down.
724 | */
725 | open func baselineOffset(_ offset: NSNumber) -> Attributer {
726 | return applyAttributes(NSAttributedString.Key.baselineOffset.rawValue, value: offset)
727 | }
728 |
729 | /**
730 | Set the kern for the active range(es)
731 | This will set the spacing between letters
732 |
733 | -parameter number: The number of pixels that will be between the letters.
734 | */
735 | open func kern(_ number: NSNumber) -> Attributer {
736 | return applyAttributes(NSAttributedString.Key.kern.rawValue, value: number)
737 | }
738 |
739 | /**
740 | Set the striketrhough for the active range(es)
741 |
742 | -parameter number: The number of pixels the strikethrough will be high.
743 | */
744 | open func strikethrough(_ number: NSNumber) -> Attributer {
745 | return applyAttributes(NSAttributedString.Key.strikethroughStyle.rawValue, value: number)
746 | }
747 |
748 | /**
749 | Set the striketrhough color
750 |
751 | -parameter color: The strikethrough color.
752 | */
753 | open func strikethroughColor(_ color: UIColor) -> Attributer {
754 | return applyAttributes(NSAttributedString.Key.strikethroughColor.rawValue, value: color)
755 | }
756 |
757 | /**
758 | Set the stroke color
759 |
760 | -parameter color: The stroke color.
761 | */
762 | open func strokeColor(_ color: UIColor) -> Attributer {
763 | return applyAttributes(NSAttributedString.Key.strokeColor.rawValue, value: color)
764 | }
765 |
766 | /**
767 | Set the stroke width
768 |
769 | -parameter number: The stroke width.
770 | */
771 | open func strokeWidth(_ number: NSNumber) -> Attributer {
772 | return applyAttributes(NSAttributedString.Key.strokeWidth.rawValue, value: number)
773 | }
774 |
775 | /**
776 | Set the style to letterpress
777 | */
778 | open var letterpress: Attributer {
779 | get {
780 | return applyAttributes(NSAttributedString.Key.textEffect.rawValue, value: NSAttributedString.TextEffectStyle.letterpressStyle as NSString)
781 | }
782 | }
783 |
784 | /**
785 | Set the obliqueness
786 |
787 | -parameter number: The obliqueness.
788 | */
789 | open func obliqueness(_ number: NSNumber) -> Attributer {
790 | return applyAttributes(NSAttributedString.Key.obliqueness.rawValue, value: number)
791 | }
792 |
793 | /**
794 | Set the expansion
795 |
796 | -parameter number: The expansion.
797 | */
798 | open func expansion(_ number: NSNumber) -> Attributer {
799 | return applyAttributes(NSAttributedString.Key.expansion.rawValue, value: number)
800 | }
801 |
802 | /**
803 | "In iOS, horizontal text is always used and specifying a different value is undefined."
804 |
805 | Set the vertical Glyph form
806 |
807 | -parameter number: The vertical glyph form.
808 | open func verticalGlyphForm(_ number: NSNumber) -> Attributer {
809 | return applyAttributes(NSVerticalGlyphFormAttributeName, value: number)
810 | }
811 | */
812 |
813 | /**
814 | Set the backgroundColor
815 |
816 | -parameter color: The color.
817 | */
818 | open func backgroundColor(_ color: UIColor) -> Attributer {
819 | return applyAttributes(NSAttributedString.Key.backgroundColor.rawValue, value: color)
820 | }
821 |
822 | /**
823 | Set the ligature
824 |
825 | -parameter number: The ligature.
826 | */
827 | open func ligature(_ number: NSNumber) -> Attributer {
828 | return applyAttributes(NSAttributedString.Key.ligature.rawValue, value: number)
829 | }
830 |
831 | /**
832 | Set the attachment
833 |
834 | -parameter attachment: The attachment.
835 | */
836 | open func attachment(_ attachment: NSTextAttachment) -> Attributer {
837 | return applyAttributes(NSAttributedString.Key.attachment.rawValue, value: attachment)
838 | }
839 |
840 | /**
841 | Set the writing directions
842 |
843 | -parameter directions: The directions.
844 | */
845 | open func writingDirection(_ directions: [NSNumber]) -> Attributer {
846 | return applyAttributes(NSAttributedString.Key.writingDirection.rawValue, value: directions as AnyObject)
847 | }
848 |
849 |
850 | // MARK: - Paragraph functions
851 |
852 | /**
853 | Set the paragraph
854 |
855 | -parameter paragraph: The paragraph style.
856 | */
857 | open func paragraph(_ paragraph: NSMutableParagraphStyle) -> Attributer {
858 | return applyAttributes(NSAttributedString.Key.paragraphStyle.rawValue, value: paragraph)
859 | }
860 |
861 | /**
862 | Apply the paragraph stylings that have been set by all the paragraph functions
863 | */
864 | open var paragraphApplyStyling: Attributer {
865 | get {
866 | return paragraph(paragraphStyle)
867 | }
868 | }
869 |
870 | /**
871 | Align the paragraph in the center
872 | */
873 | open var paragraphAlignCenter: Attributer {
874 | get {
875 | paragraphStyle.alignment = NSTextAlignment.center
876 | return self
877 | }
878 | }
879 |
880 | /**
881 | Align the paragraph right
882 | */
883 | open var paragraphAlignRight: Attributer {
884 | get {
885 | paragraphStyle.alignment = NSTextAlignment.right
886 | return self
887 | }
888 | }
889 |
890 | /**
891 | Align the paragraph left
892 | */
893 | open var paragraphAlignLeft: Attributer {
894 | get {
895 | paragraphStyle.alignment = NSTextAlignment.left
896 | return self
897 | }
898 | }
899 |
900 | /**
901 | Align the paragraph justified
902 | */
903 | open var paragraphAlignJustified: Attributer {
904 | get {
905 | paragraphStyle.alignment = NSTextAlignment.justified
906 | return self
907 | }
908 | }
909 |
910 | /**
911 | Align the paragraph Natural
912 | */
913 | open var paragraphAlignNatural: Attributer {
914 | get {
915 | paragraphStyle.alignment = NSTextAlignment.natural
916 | return self
917 | }
918 | }
919 |
920 | /**
921 | Set the paragraph line spacing
922 | */
923 | open func paragraphLineSpacing(_ number: CGFloat) -> Attributer {
924 | paragraphStyle.lineSpacing = number
925 | return self
926 | }
927 |
928 | /**
929 | Set the paragraph spacing
930 | */
931 | open func paragraphSpacing(_ number: CGFloat) -> Attributer {
932 | paragraphStyle.paragraphSpacing = number
933 | return self
934 | }
935 |
936 | /**
937 | Set the paragraph first line head indent
938 | */
939 | open func paragraphFirstLineHeadIndent(_ number: CGFloat) -> Attributer {
940 | paragraphStyle.firstLineHeadIndent = number
941 | return self
942 | }
943 |
944 | /**
945 | Set the paragraph head indent
946 | */
947 | open func paragraphHeadIndent(_ number: CGFloat) -> Attributer {
948 | paragraphStyle.headIndent = number
949 | return self
950 | }
951 |
952 | /**
953 | Set the paragraph tail indent
954 | */
955 | open func paragraphTailIndent(_ number: CGFloat) -> Attributer {
956 | paragraphStyle.tailIndent = number
957 | return self
958 | }
959 |
960 | /**
961 | Set the paragraph linebreak mode to word wrapping
962 | */
963 | open var paragraphLineBreakModeWordWrapping: Attributer {
964 | get {
965 | paragraphStyle.lineBreakMode = NSLineBreakMode.byWordWrapping
966 | return self
967 | }
968 | }
969 |
970 | /**
971 | Set the paragraph linebreak mode to character wrapping
972 | */
973 | open var paragraphLineBreakModeCharWrapping: Attributer {
974 | get {
975 | paragraphStyle.lineBreakMode = NSLineBreakMode.byCharWrapping
976 | return self
977 | }
978 | }
979 |
980 | /**
981 | Set the paragraph linebreak mode to clipping
982 | */
983 | open var paragraphLineBreakModeClipping: Attributer {
984 | get {
985 | paragraphStyle.lineBreakMode = NSLineBreakMode.byClipping
986 | return self
987 | }
988 | }
989 |
990 | /**
991 | Set the paragraph linebreak mode to truncate head
992 | */
993 | open var paragraphLineBreakTruncatingHead: Attributer {
994 | get {
995 | paragraphStyle.lineBreakMode = NSLineBreakMode.byTruncatingHead
996 | return self
997 | }
998 | }
999 |
1000 | /**
1001 | Set the paragraph linebreak mode to truncate tail
1002 | */
1003 | open var paragraphLineBreakTruncatingTail: Attributer {
1004 | get {
1005 | paragraphStyle.lineBreakMode = NSLineBreakMode.byTruncatingTail
1006 | return self
1007 | }
1008 | }
1009 |
1010 | /**
1011 | Set the paragraph linebreak mode to truncate midle
1012 | */
1013 | open var paragraphLineBreakTruncatingMiddle: Attributer {
1014 | get {
1015 | paragraphStyle.lineBreakMode = NSLineBreakMode.byTruncatingMiddle
1016 | return self
1017 | }
1018 | }
1019 |
1020 | /**
1021 | Set the paragraph minimum line height
1022 | */
1023 | open func paragraphMinimumLineHeight(_ number: CGFloat) -> Attributer {
1024 | paragraphStyle.minimumLineHeight = number
1025 | return self
1026 | }
1027 |
1028 | /**
1029 | Set the paragraph maximum line height
1030 | */
1031 | open func paragraphMaximumLineHeight(_ number: CGFloat) -> Attributer {
1032 | paragraphStyle.maximumLineHeight = number
1033 | return self
1034 | }
1035 |
1036 | /**
1037 | Set the paragraph base writing direction to natural
1038 | */
1039 | open var paragraphBaseWritingDirectionNatural: Attributer {
1040 | get {
1041 | paragraphStyle.baseWritingDirection = NSWritingDirection.natural
1042 | return self
1043 | }
1044 | }
1045 |
1046 | /**
1047 | Set the paragraph base writing direction to left to right
1048 | */
1049 | open var paragraphBaseWritingDirectionLeftToRight: Attributer {
1050 | get {
1051 | paragraphStyle.baseWritingDirection = NSWritingDirection.leftToRight
1052 | return self
1053 | }
1054 | }
1055 |
1056 | /**
1057 | Set the paragraph base writing direction to right to left
1058 | */
1059 | open var paragraphBaseWritingDirectionRightToLeft: Attributer {
1060 | get {
1061 | paragraphStyle.baseWritingDirection = NSWritingDirection.rightToLeft
1062 | return self
1063 | }
1064 | }
1065 |
1066 | /**
1067 | Set the paragraph line hight multiple
1068 | */
1069 | open func paragraphLineHeightMultiple(_ number: CGFloat) -> Attributer {
1070 | paragraphStyle.lineHeightMultiple = number
1071 | return self
1072 | }
1073 |
1074 | /**
1075 | Set the paragraph spacing before
1076 | */
1077 | open func paragraphSpacingBefore(_ number: CGFloat) -> Attributer {
1078 | paragraphStyle.paragraphSpacingBefore = number
1079 | return self
1080 | }
1081 |
1082 | /**
1083 | Set the paragraph hyphenation factor
1084 | */
1085 | open func paragraphHyphenationFactor(_ number: Float) -> Attributer {
1086 | paragraphStyle.hyphenationFactor = number
1087 | return self
1088 | }
1089 |
1090 | /* Someone want this?
1091 | @available(tvOS 7.0, *)
1092 | open var tabStops: [NSTextTab]!
1093 |
1094 | @available(tvOS 7.0, *)
1095 | open var defaultTabInterval: CGFloat
1096 |
1097 | @available(tvOS 9.0, *)
1098 | open var allowsDefaultTighteningForTruncation: Bool
1099 |
1100 |
1101 | @available(tvOS 9.0, *)
1102 | open func addTabStop(_ anObject: NSTextTab)
1103 |
1104 | @available(tvOS 9.0, *)
1105 | open func removeTabStop(_ anObject: NSTextTab)
1106 |
1107 |
1108 | @available(tvOS 9.0, *)
1109 | open func setParagraphStyle(_ obj: NSParagraphStyle)
1110 | */
1111 |
1112 |
1113 | // MARK: - Private
1114 |
1115 | @discardableResult
1116 | fileprivate func applyAttributes(_ attributeName: String, value: AnyObject) -> Attributer {
1117 | for range in self.ranges {
1118 | guard range.location != NSNotFound else { return self }
1119 | self.attributedText.addAttribute(NSAttributedString.Key(rawValue: attributeName), value: value, range: range)
1120 | }
1121 | return self
1122 | }
1123 |
1124 | open func colorFrom(hex: Int) -> UIColor {
1125 | return UIColor(hex: hex)
1126 | }
1127 |
1128 |
1129 | private static var cachedRegularExpressions: [String : NSRegularExpression] = [:]
1130 | private static func regularExpression(for pattern: String) -> NSRegularExpression? {
1131 | if let regex = cachedRegularExpressions[pattern] {
1132 | return regex
1133 | } else if let createdRegex = try? NSRegularExpression(pattern: pattern, options: [.caseInsensitive]) {
1134 | cachedRegularExpressions[pattern] = createdRegex
1135 | return createdRegex
1136 | } else {
1137 | return nil
1138 | }
1139 | }
1140 | }
1141 |
1142 | /**
1143 | opperator for easy execution of the .append
1144 | */
1145 | public func + (left: Attributer, right: Attributer) -> Attributer {
1146 | return left.append(right)
1147 | }
1148 |
1149 | /**
1150 | Helper extension for converting a NSRange to a Range
1151 | */
1152 | public extension String {
1153 | /**
1154 | Helper extension for converting a NSRange to a Range
1155 |
1156 | -parameter nsRange: The NSRange that needs to be converted
1157 | */
1158 | func range(from nsRange: NSRange) -> Range? {
1159 | guard
1160 | let from16 = utf16.index(utf16.startIndex, offsetBy: nsRange.location, limitedBy: utf16.endIndex),
1161 | let to16 = utf16.index(from16, offsetBy: nsRange.length, limitedBy: utf16.endIndex),
1162 | let from = String.Index(from16, within: self),
1163 | let to = String.Index(to16, within: self)
1164 | else { return nil }
1165 | return from ..< to
1166 | }
1167 | }
1168 |
1169 | /**
1170 | Helper extension for creating a UIColor based on a hex value
1171 | */
1172 | public extension UIColor {
1173 | /**
1174 | Helper extension for creating a UIColor based on a hex value
1175 |
1176 | -parameter hex: The hex value (like 0xffffff) that wil be used for the color
1177 | */
1178 | convenience init(hex: Int) {
1179 | let red = CGFloat((hex & 0xff0000) >> 16) / 255.0
1180 | let green = CGFloat((hex & 0x00ff00) >> 8) / 255.0
1181 | let blue = CGFloat(hex & 0x0000ff) / 255.0
1182 | self.init(red: red, green: green, blue: blue, alpha: 1.0)
1183 | }
1184 | }
1185 |
1186 |
1187 |
--------------------------------------------------------------------------------
/Sources/CGFloatDP.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CGFloatDP.swift
3 | // AttributedTextView
4 | //
5 | // Created by Vermeer, Edwin on 05/10/2017.
6 | // Copyright © 2017 evermeer. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension CGFloat {
12 | /**
13 | The relative dimension to the corresponding screen size.
14 |
15 | //Usage
16 | let someView = UIView(frame: CGRect(x: 0, y: 0, width: 320.dp, height: 40.dp)
17 |
18 | **Warning** Only works with size references from @1x mockups.
19 |
20 | The 320 corresponds with your design size. You can change it to:
21 | iPhone 6, 7, 8 Plus — 414
22 | iPhone 6, 7, 8, X — 375
23 |
24 | */
25 | var dp: CGFloat {
26 | return (self / 320) * UIScreen.main.bounds.width
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Sources/Info-tvOS.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 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSPrincipalClass
22 |
23 | UIRequiredDeviceCapabilities
24 |
25 | arm64
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/Sources/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 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | NSPrincipalClass
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/Sources/NSAttributedString+Html.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSAttributedString+Html.swift
3 | // AttributedTextView-iOS
4 | //
5 | // Created by Edwin Vermeer on 07-03-18.
6 | // Copyright © 2018 evermeer. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | public extension NSMutableAttributedString {
11 | internal convenience init?(html: String) {
12 | guard let data = html.data(using: String.Encoding.utf16, allowLossyConversion: false) else {
13 | return nil
14 | }
15 |
16 | guard let attributedString = try? NSMutableAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html, .characterEncoding: String.Encoding.utf8.rawValue], documentAttributes: nil) else {
17 | return nil
18 | }
19 |
20 | self.init(attributedString: attributedString)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Sources/String+Attributer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+Attributer.swift
3 | //
4 | // Created by Edwin Vermeer on 25/11/2016.
5 | // Copyright © 2016 Edwin Vermeer. All rights reserved.
6 | //
7 |
8 | import UIKit
9 |
10 | public extension String {
11 |
12 | /**
13 | Easy access to the Attributer object
14 | */
15 | var attributer: Attributer {
16 | get {
17 | return Attributer(self)
18 | }
19 | }
20 |
21 | // All functions below just forward the call to the Attributer
22 |
23 |
24 | // MARK: - Color functions
25 |
26 | /**
27 | Apply the color black to the active range(es)
28 | */
29 | var black: Attributer {
30 | get {
31 | return attributer.black
32 | }
33 | }
34 |
35 | /**
36 | Apply the color darkGray to the active range(es)
37 | */
38 | var darkGray: Attributer {
39 | get {
40 | return attributer.darkGray
41 | }
42 | }
43 |
44 | /**
45 | Apply the color lightGray to the active range(es)
46 | */
47 | var lightGray: Attributer {
48 | get {
49 | return attributer.lightGray
50 | }
51 | }
52 |
53 | /**
54 | Apply the color white to the active range(es)
55 | */
56 | var white: Attributer {
57 | get {
58 | return attributer.white
59 | }
60 | }
61 |
62 | /**
63 | Apply the color gray to the active range(es)
64 | */
65 | var gray: Attributer {
66 | get {
67 | return attributer.gray
68 | }
69 | }
70 |
71 | /**
72 | Apply the color red to the active range(es)
73 | */
74 | var red: Attributer {
75 | get {
76 | return attributer.red
77 | }
78 | }
79 |
80 | /**
81 | Apply the color green to the active range(es)
82 | */
83 | var green: Attributer {
84 | get {
85 | return attributer.green
86 | }
87 | }
88 |
89 | /**
90 | Apply the color blue to the active range(es)
91 | */
92 | var blue: Attributer {
93 | get {
94 | return attributer.blue
95 | }
96 | }
97 |
98 | /**
99 | Apply the color cyan to the active range(es)
100 | */
101 | var cyan: Attributer {
102 | get {
103 | return attributer.cyan
104 | }
105 | }
106 |
107 | /**
108 | Apply the color yellow to the active range(es)
109 | */
110 | var yellow: Attributer {
111 | get {
112 | return attributer.yellow
113 | }
114 | }
115 |
116 | /**
117 | Apply the color magenta to the active range(es)
118 | */
119 | var magenta: Attributer {
120 | get {
121 | return attributer.magenta
122 | }
123 | }
124 |
125 | /**
126 | Apply the color orange to the active range(es)
127 | */
128 | var orange: Attributer {
129 | get {
130 | return attributer.orange
131 | }
132 | }
133 |
134 | /**
135 | Apply the color purple to the active range(es)
136 | */
137 | var purple: Attributer {
138 | get {
139 | return attributer.purple
140 | }
141 | }
142 |
143 | /**
144 | Apply the color brown to the active range(es)
145 | */
146 | var brown: Attributer {
147 | get {
148 | return attributer.brown
149 | }
150 | }
151 |
152 | /**
153 | Apply the color clear to the active range(es)
154 | */
155 | var clear: Attributer {
156 | get {
157 | return attributer.clear
158 | }
159 | }
160 |
161 | /**
162 | Apply a UIColor to the active range(es)
163 |
164 | -parameter color: The UIColor that will be applied.
165 | */
166 | func color(_ color: UIColor) -> Attributer {
167 | return attributer.color(color)
168 | }
169 |
170 | /**
171 | Apply a color from a hex value to the active range(es)
172 |
173 | -parameter hex: The hex value of the color that will be applied.
174 | */
175 | func color(_ hex: Int) -> Attributer {
176 | return attributer.color(hex)
177 | }
178 |
179 | // MARK: - Selection functions
180 |
181 | /**
182 | Make the active range the entire text
183 | */
184 | var all: Attributer {
185 | get {
186 | return attributer.all
187 | }
188 | }
189 |
190 | /**
191 | Make the active range (from and to)
192 |
193 | -parameter from: The start of the new range
194 | -parameter to: The end of the new range.
195 | */
196 | func range(_ from: Int, to: Int) -> Attributer {
197 | return attributer.range(from, to: to)
198 | }
199 |
200 | /**
201 | Make the active range (location and length)
202 |
203 | -parameter location: The location of the new range
204 | -parameter length: The length of the new range.
205 | */
206 | func range(_ location: Int, length: Int) -> Attributer {
207 | return attributer.range(location, length: length)
208 | }
209 |
210 | /**
211 | Just set the active range
212 |
213 | -parameter range: The new range.
214 | */
215 | func range(_ range: NSRange) -> Attributer {
216 | return attributer.range(range)
217 | }
218 |
219 | /**
220 | Find the first occurrance of a string (search with using .CompareOptions)
221 |
222 | -parameter substring: The string to search for.
223 | -parameter options: The search options
224 | */
225 | func matchWithOptions(_ substring: String, _ options: NSString.CompareOptions = .literal) -> Attributer {
226 | return attributer.matchWithOptions(substring, options)
227 | }
228 |
229 | /**
230 | Find the first occurrance of a string
231 |
232 | -parameter substring: The string to search for.
233 | */
234 | func match(_ substring: String) -> Attributer {
235 | return attributer.match(substring)
236 | }
237 |
238 | /**
239 | Find the all occurrances of a string (search with using .CompareOptions)
240 |
241 | -parameter substring: The string to search for.
242 | -parameter options: The search options
243 | */
244 | func matchAllWithOptions(_ substring: String, _ options: NSString.CompareOptions = .literal) -> Attributer {
245 | return attributer.matchAllWithOptions(substring, options)
246 | }
247 |
248 | /**
249 | Find the all occurrances of a string
250 |
251 | -parameter substring: The string to search for.
252 | */
253 | func matchAll(_ substring: String) -> Attributer {
254 | return attributer.matchAll(substring)
255 | }
256 |
257 | /**
258 | Find the all hashtags (words beginning with #)
259 | */
260 | var matchHashtags: Attributer {
261 | get {
262 | return attributer.matchHashtags
263 | }
264 | }
265 |
266 | /**
267 | Find the all mentions (words beginning with @)
268 | */
269 | var matchMentions: Attributer {
270 | get {
271 | return attributer.matchMentions
272 | }
273 | }
274 |
275 | /**
276 | Find the all links
277 | */
278 | var matchLinks: Attributer {
279 | get {
280 | return attributer.matchLinks
281 | }
282 | }
283 |
284 | /**
285 | Use a regular expression patter to find ranges
286 |
287 | -parameter pattern: The regex pattern.
288 | */
289 | func matchPattern(_ pattern: String) -> Attributer {
290 | return attributer.matchPattern(pattern)
291 | }
292 |
293 | /**
294 | Append a string to the current attributed text and select the new string as the active range
295 |
296 | -parameter string: The string that will be added
297 | */
298 | func append(string: String) -> Attributer {
299 | return attributer.append(string)
300 | }
301 |
302 | /**
303 | Append an attributed string to the current attributed text and select the new string as the active range
304 |
305 | -parameter attributedString: The attributed string that will be added
306 | */
307 | func append(_ attributedString: NSMutableAttributedString) -> Attributer {
308 | return attributer.append(attributedString)
309 | }
310 |
311 | /**
312 | Append an Attributer to the current and select the new string as the active range
313 |
314 | -parameter attributer: The Attributer that will be added
315 | */
316 | func append(_ attributer: Attributer) -> Attributer {
317 | return attributer.append(attributer)
318 | }
319 |
320 |
321 | // MARK: - Font
322 |
323 | /**
324 | Apply a font to the active range
325 |
326 | -parameter fontName: The name of the font that will be applied
327 | */
328 | func fontName(_ fontName: String) -> Attributer {
329 | return attributer.fontName(fontName)
330 | }
331 |
332 | /**
333 | Apply a fontsize to the active range
334 |
335 | -parameter size: The fontsize that will be applied
336 | */
337 | func size(_ size: CGFloat) -> Attributer {
338 | return attributer.size(size)
339 | }
340 |
341 | /**
342 | Apply a font to the active range
343 |
344 | -parameter font: The UIFont that will be applied
345 | */
346 | func font(_ font: UIFont) -> Attributer {
347 | return attributer.font(font)
348 | }
349 |
350 |
351 | // MARK: - Create a link
352 |
353 | /**
354 | Make the current active range interact. A touch will call the UITextViewDelegate which is handled in the AttributedTextView
355 |
356 | -parameter scheme: The scheme that will be added in front of the link
357 | */
358 | @available(*, deprecated, message: "use AttributedTextView with makeInteract: instead")
359 | func makeInteractWithURLforScheme(_ scheme: String) -> Attributer {
360 | return attributer.makeInteractWithURLforScheme(scheme)
361 | }
362 |
363 | /**
364 | Make the current active range interact. A touch will call the UITextViewDelegate. When using the AttributedTextView the callback function will be called
365 |
366 | -parameter callback: The callback function that will be called when using AttributedTextView
367 | */
368 | func makeInteract(_ callback: @escaping ((_ link: String) -> ())) -> Attributer {
369 | return attributer.makeInteract(callback)
370 | }
371 |
372 | /**
373 | Used for setting the link color of the UITextView
374 |
375 | -parameter color: The color of all links
376 | */
377 | func setLinkColor(_ color: UIColor) -> Attributer {
378 | return attributer.setLinkColor(color)
379 | }
380 |
381 | /**
382 | Called by AttributedTextView when a URL is touched
383 |
384 | -parameter URL: The URL that was touched
385 | */
386 | func interactWithURL(URL: URL) {
387 | return attributer.interactWithURL(URL: URL)
388 | }
389 |
390 | /**
391 | Make the current selected range a link
392 |
393 | -parameter link: The URL that will be forwarded to the UITextViewDelegate
394 | */
395 | func link(_ link: URL) -> Attributer {
396 | return attributer.link(link)
397 | }
398 |
399 | /**
400 | Make the current selected range a link
401 |
402 | -parameter link: The NSString that will be forwarded to the UITextViewDelegate
403 | */
404 | func link(_ link: NSString) -> Attributer {
405 | return attributer.link(link)
406 | }
407 |
408 | // MARK: - Style
409 |
410 | /**
411 | underline the active range
412 | */
413 | var underline: Attributer {
414 | return attributer.underline
415 | }
416 |
417 | /**
418 | set the underline style for the active range (you also have to call .underline)
419 |
420 | -parameter underline: The underline style
421 | */
422 | func underline(_ underline: NSUnderlineStyle) -> Attributer {
423 | return attributer.underline(underline)
424 | }
425 |
426 | /**
427 | set the underline style for the active range (you also have to call .underline)
428 |
429 | -parameter style: The underline style
430 | -parameter pattern : The underline pattern
431 | */
432 | func underline(_ style: NSUnderlineStyle, _ pattern: NSUnderlineStyle) -> Attributer {
433 | return attributer.underline(style, pattern)
434 | }
435 |
436 | /**
437 | set the underline color for the active range (you also have to call .underline)
438 |
439 | -parameter color: the UIColor of the undeline
440 | */
441 | func underline(_ color: UIColor) -> Attributer {
442 | return attributer.underline(color)
443 | }
444 |
445 | /**
446 | set the underline color for the active range (you also have to call .underline)
447 |
448 | -parameter hex: the hex value of the color for the underline
449 | */
450 | func underline(_ hex: Int) -> Attributer {
451 | return attributer.underline(hex)
452 | }
453 |
454 | /**
455 | Attach an image to the range
456 |
457 | -parameter image: the UIImage that will be used as the attachment
458 | */
459 | func attach(_ image: UIImage?, bounds: CGRect? = nil) -> Attributer {
460 | return attributer.attach(image, bounds: bounds)
461 | }
462 |
463 | /**
464 | Attach an image to the range
465 |
466 | -parameter imageStr: the name of the image that will be used as the attachment
467 | */
468 | func attach(_ imageStr: String, bounds: CGRect? = nil) -> Attributer {
469 | return attributer.attach(imageStr, bounds: bounds)
470 | }
471 |
472 | /**
473 | Set the shadow for the active range(es)
474 |
475 | -parameter shadow: The NSShadow that will be set
476 | */
477 | func shadow(_ shadow: NSShadow) -> Attributer {
478 | return attributer.shadow(shadow)
479 | }
480 |
481 | /**
482 | Set the shadow for the active range(es)
483 |
484 | -parameter color: The UIColor for the shadow
485 | -parameter offset: The CGSize offset for the shadow
486 | -parameter blurRadius: The blurRadius for the shadow
487 | */
488 | func shadow(color: UIColor?, offset: CGSize, blurRadius: CGFloat) -> Attributer {
489 | return attributer.shadow(color: color, offset: offset, blurRadius: blurRadius)
490 | }
491 |
492 | /**
493 | Set the baseline offset for the active range(es)
494 | This will move the text up or down
495 |
496 | -parameter offset: The number of pixels that the text will be moved up or down.
497 | */
498 | func baselineOffset(_ offset: NSNumber) -> Attributer {
499 | return attributer.baselineOffset(offset)
500 | }
501 |
502 | /**
503 | Set the kern for the active range(es)
504 | This will set the spacing between letters
505 |
506 | -parameter number: The number of pixels that will be between the letters.
507 | */
508 | func kern(_ number: NSNumber) -> Attributer {
509 | return attributer.kern(number)
510 | }
511 |
512 | /**
513 | Set the striketrhough for the active range(es)
514 |
515 | -parameter number: The number of pixels the strikethrough will be high.
516 | */
517 | func strikethrough(_ number: NSNumber) -> Attributer {
518 | return attributer.strikethrough(number)
519 | }
520 |
521 |
522 | /**
523 | Set the striketrhough color for the active range(es)
524 |
525 | -parameter color: The color of the striketrhough.
526 | */
527 | func strikethroughColor(_ color: UIColor) -> Attributer {
528 | return attributer.strikethroughColor(color)
529 | }
530 |
531 | /**
532 | Set the stroke color
533 |
534 | -parameter color: The stroke color.
535 | */
536 | func strokeColor(_ color: UIColor) -> Attributer {
537 | return attributer.strokeColor(color)
538 | }
539 |
540 | /**
541 | Set the stroke width
542 |
543 | -parameter number: The stroke width.
544 | */
545 | func strokeWidth(_ number: NSNumber) -> Attributer {
546 | return attributer.strokeWidth(number)
547 | }
548 |
549 | /**
550 | Set the style to letterpress
551 | */
552 | var letterpress: Attributer {
553 | get {
554 | return attributer.letterpress
555 | }
556 | }
557 |
558 | /**
559 | Set the obliqueness
560 |
561 | -parameter number: The obliqueness.
562 | */
563 | func obliqueness(_ number: NSNumber) -> Attributer {
564 | return attributer.obliqueness(number)
565 | }
566 |
567 | /**
568 | Set the expansion
569 |
570 | -parameter number: The expansion.
571 | */
572 | func expansion(_ number: NSNumber) -> Attributer {
573 | return attributer.expansion(number)
574 | }
575 |
576 | /*
577 | "In iOS, horizontal text is always used and specifying a different value is undefined."
578 |
579 | public func verticalGlyphForm(_ number: NSNumber) -> Attributer {
580 | return attributer.verticalGlyphForm(number)
581 | }
582 | */
583 |
584 | /**
585 | Set the backgroundColor
586 |
587 | -parameter color: The color.
588 | */
589 | func backgroundColor(_ color: UIColor) -> Attributer {
590 | return attributer.backgroundColor(color)
591 | }
592 |
593 | /**
594 | Set the ligature
595 |
596 | -parameter number: The ligature.
597 | */
598 | func ligature(_ number: NSNumber) -> Attributer {
599 | return attributer.ligature(number)
600 | }
601 |
602 | /**
603 | Set the attachment
604 |
605 | -parameter attachment: The attachment.
606 | */
607 | func attachment(_ attachment: NSTextAttachment) -> Attributer {
608 | return attributer.attachment(attachment)
609 | }
610 |
611 | /**
612 | Set the writing directions
613 |
614 | -parameter directions: The directions.
615 | */
616 | func writingDirection(_ directions: [NSNumber]) -> Attributer {
617 | return attributer.writingDirection(directions)
618 | }
619 |
620 |
621 | // MARK: - Paragraph functions
622 |
623 | /**
624 | Set the paragraph
625 |
626 | -parameter paragraph: The paragraph style.
627 | */
628 | func paragraph(_ paragraph: NSMutableParagraphStyle) -> Attributer {
629 | return attributer.paragraph(paragraph)
630 | }
631 |
632 | /**
633 | Apply the paragraph stylings that have been set by all the paragraph functions
634 | */
635 | var paragraphApplyStyling: Attributer {
636 | get {
637 | return attributer.paragraphApplyStyling
638 | }
639 | }
640 |
641 | /**
642 | Align the paragraph in the center
643 | */
644 | var paragraphAlignCenter: Attributer {
645 | get {
646 | return attributer.paragraphAlignCenter
647 | }
648 | }
649 |
650 | /**
651 | Align the paragraph right
652 | */
653 | var paragraphAlignRight: Attributer {
654 | get {
655 | return attributer.paragraphAlignRight
656 | }
657 | }
658 |
659 | /**
660 | Align the paragraph left
661 | */
662 | var paragraphAlignLeft: Attributer {
663 | get {
664 | return attributer.paragraphAlignLeft
665 | }
666 | }
667 |
668 | /**
669 | Align the paragraph justified
670 | */
671 | var paragraphAlignJustified: Attributer {
672 | get {
673 | return attributer.paragraphAlignJustified
674 | }
675 | }
676 |
677 | /**
678 | Align the paragraph Natural
679 | */
680 | var paragraphAlignNatural: Attributer {
681 | get {
682 | return attributer.paragraphAlignNatural
683 | }
684 | }
685 |
686 | /**
687 | Set the paragraph line spacing
688 | */
689 | func paragraphLineSpacing(_ number: CGFloat) -> Attributer {
690 | return attributer.paragraphLineSpacing(number)
691 | }
692 |
693 | /**
694 | Set the paragraph spacing
695 | */
696 | func paragraphSpacing(_ number: CGFloat) -> Attributer {
697 | return attributer.paragraphSpacing(number)
698 | }
699 |
700 | /**
701 | Set the paragraph first line head indent
702 | */
703 | func paragraphFirstLineHeadIndent(_ number: CGFloat) -> Attributer {
704 | return attributer.paragraphFirstLineHeadIndent(number)
705 | }
706 |
707 | /**
708 | Set the paragraph head indent
709 | */
710 | func paragraphHeadIndent(_ number: CGFloat) -> Attributer {
711 | return attributer.paragraphHeadIndent(number)
712 | }
713 |
714 | /**
715 | Set the paragraph tail indent
716 | */
717 | func paragraphTailIndent(_ number: CGFloat) -> Attributer {
718 | return attributer.paragraphTailIndent(number)
719 | }
720 |
721 | /**
722 | Set the paragraph linebreak mode to word wrapping
723 | */
724 | var paragraphLineBreakModeWordWrapping: Attributer {
725 | get {
726 | return attributer.paragraphLineBreakModeWordWrapping
727 | }
728 | }
729 |
730 | /**
731 | Set the paragraph linebreak mode to character wrapping
732 | */
733 | var paragraphLineBreakModeCharWrapping: Attributer {
734 | get {
735 | return attributer.paragraphLineBreakModeCharWrapping
736 | }
737 | }
738 |
739 | /**
740 | Set the paragraph linebreak mode to clipping
741 | */
742 | var paragraphLineBreakModeClipping: Attributer {
743 | get {
744 | return attributer.paragraphLineBreakModeClipping
745 | }
746 | }
747 |
748 | /**
749 | Set the paragraph linebreak mode to truncate head
750 | */
751 | var paragraphLineBreakTruncatingHead: Attributer {
752 | get {
753 | return attributer.paragraphLineBreakTruncatingHead
754 | }
755 | }
756 |
757 | /**
758 | Set the paragraph linebreak mode to truncate tail
759 | */
760 | var paragraphLineBreakTruncatingTail: Attributer {
761 | get {
762 | return attributer.paragraphLineBreakTruncatingTail
763 | }
764 | }
765 |
766 | /**
767 | Set the paragraph linebreak mode to truncate midle
768 | */
769 | var paragraphLineBreakTruncatingMiddle: Attributer {
770 | get {
771 | return attributer.paragraphLineBreakTruncatingMiddle
772 | }
773 | }
774 |
775 | /**
776 | Set the paragraph minimum line height
777 | */
778 | func paragraphMinimumLineHeight(_ number: CGFloat) -> Attributer {
779 | return attributer.paragraphMinimumLineHeight(number)
780 | }
781 |
782 | /**
783 | Set the paragraph maximum line height
784 | */
785 | func paragraphMaximumLineHeight(_ number: CGFloat) -> Attributer {
786 | return attributer.paragraphMaximumLineHeight(number)
787 | }
788 |
789 | /**
790 | Set the paragraph base writing direction to natural
791 | */
792 | var paragraphBaseWritingDirectionNatural: Attributer {
793 | get {
794 | return attributer.paragraphBaseWritingDirectionNatural
795 | }
796 | }
797 |
798 | /**
799 | Set the paragraph base writing direction to left to right
800 | */
801 | var paragraphBaseWritingDirectionLeftToRight: Attributer {
802 | get {
803 | return attributer.paragraphBaseWritingDirectionLeftToRight
804 | }
805 | }
806 |
807 | /**
808 | Set the paragraph base writing direction to right to left
809 | */
810 | var paragraphBaseWritingDirectionRightToLeft: Attributer {
811 | get {
812 | return attributer.paragraphBaseWritingDirectionRightToLeft
813 | }
814 | }
815 |
816 | /**
817 | Set the paragraph line hight multiple
818 | */
819 | func paragraphLineHeightMultiple(_ number: CGFloat) -> Attributer {
820 | return attributer.paragraphLineHeightMultiple(number)
821 | }
822 |
823 | /**
824 | Set the paragraph spacing before
825 | */
826 | func paragraphSpacingBefore(_ number: CGFloat) -> Attributer {
827 | return attributer.paragraphSpacingBefore(number)
828 | }
829 |
830 | /**
831 | Set the paragraph hyphenation factor
832 | */
833 | func paragraphHyphenationFactor(_ number: Float) -> Attributer {
834 | return attributer.paragraphHyphenationFactor(number)
835 | }
836 |
837 | var html: Attributer { get {
838 | return Attributer(NSMutableAttributedString(html: self) ?? NSMutableAttributedString())
839 | }
840 | }
841 |
842 | /* Someone want this?
843 | @available(tvOS 7.0, *)
844 | public var tabStops: [NSTextTab]!
845 |
846 | @available(tvOS 7.0, *)
847 | public var defaultTabInterval: CGFloat
848 |
849 | @available(tvOS 9.0, *)
850 | public var allowsDefaultTighteningForTruncation: Bool
851 |
852 |
853 | @available(tvOS 9.0, *)
854 | public func addTabStop(_ anObject: NSTextTab)
855 |
856 | @available(tvOS 9.0, *)
857 | public func removeTabStop(_ anObject: NSTextTab)
858 |
859 |
860 | @available(tvOS 9.0, *)
861 | public func setParagraphStyle(_ obj: NSParagraphStyle)
862 | */
863 | }
864 |
--------------------------------------------------------------------------------
/Sources/String+NSRange.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+NSRange.swift
3 | // AttributedTextView-iOS
4 | //
5 | // Created by Edwin Vermeer on 18-06-18.
6 | // Copyright © 2018 evermeer. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | public extension String {
12 | func substring(with nsrange: NSRange) -> Substring? {
13 | guard let range = Range(nsrange, in: self) else { return nil }
14 | return self[range]
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/scripts/bootstrap:
--------------------------------------------------------------------------------
1 | carthage update Nimble Quick --no-use-binaries
2 |
--------------------------------------------------------------------------------