├── .github
└── FUNDING.yml
├── .gitignore
├── .swiftpm
└── xcode
│ └── package.xcworkspace
│ └── contents.xcworkspacedata
├── CHANGELOG.md
├── CONTRIBUTING.md
├── Configs
├── SwiftRichString.plist
└── SwiftRichStringTests.plist
├── DemoApp
├── AppDelegate.swift
├── Assets.xcassets
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
├── Info.plist
└── ViewController.swift
├── Documentation_Assests
├── Logo.sketch
├── image_1.png
├── image_2.png
├── image_3.png
├── image_4.png
├── image_5.png
└── image_6.png
├── ExampleMac
├── AppDelegate.swift
├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ └── Contents.json
├── Base.lproj
│ └── Main.storyboard
├── ExampleMac.entitlements
├── Info.plist
└── ViewController.swift
├── ExampleTvOS
├── AppDelegate.swift
├── Assets.xcassets
│ ├── App Icon & Top Shelf Image.brandassets
│ │ ├── App Icon - App Store.imagestack
│ │ │ ├── Back.imagestacklayer
│ │ │ │ ├── Content.imageset
│ │ │ │ │ └── Contents.json
│ │ │ │ └── Contents.json
│ │ │ ├── Contents.json
│ │ │ ├── Front.imagestacklayer
│ │ │ │ ├── Content.imageset
│ │ │ │ │ └── Contents.json
│ │ │ │ └── Contents.json
│ │ │ └── Middle.imagestacklayer
│ │ │ │ ├── Content.imageset
│ │ │ │ └── Contents.json
│ │ │ │ └── Contents.json
│ │ ├── App Icon.imagestack
│ │ │ ├── Back.imagestacklayer
│ │ │ │ ├── Content.imageset
│ │ │ │ │ └── Contents.json
│ │ │ │ └── Contents.json
│ │ │ ├── Contents.json
│ │ │ ├── Front.imagestacklayer
│ │ │ │ ├── Content.imageset
│ │ │ │ │ └── Contents.json
│ │ │ │ └── Contents.json
│ │ │ └── Middle.imagestacklayer
│ │ │ │ ├── Content.imageset
│ │ │ │ └── Contents.json
│ │ │ │ └── Contents.json
│ │ ├── Contents.json
│ │ ├── Top Shelf Image Wide.imageset
│ │ │ └── Contents.json
│ │ └── Top Shelf Image.imageset
│ │ │ └── Contents.json
│ ├── Contents.json
│ └── Launch Image.launchimage
│ │ └── Contents.json
├── Base.lproj
│ └── Main.storyboard
├── Info.plist
└── ViewController.swift
├── ExampleWatchOS Extension
├── Assets.xcassets
│ ├── Complication.complicationset
│ │ ├── Circular.imageset
│ │ │ └── Contents.json
│ │ ├── Contents.json
│ │ ├── Extra Large.imageset
│ │ │ └── Contents.json
│ │ ├── Modular.imageset
│ │ │ └── Contents.json
│ │ └── Utilitarian.imageset
│ │ │ └── Contents.json
│ └── Contents.json
├── ExtensionDelegate.swift
├── Info.plist
├── InterfaceController.swift
├── NotificationController.swift
└── PushNotificationPayload.apns
├── ExampleWatchOS
├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ └── Contents.json
├── Base.lproj
│ └── Interface.storyboard
└── Info.plist
├── ExampleiOS
├── AppDelegate.swift
├── Assets.xcassets
│ ├── AppIcon.appiconset
│ │ └── Contents.json
│ ├── Contents.json
│ └── therocket.imageset
│ │ ├── 816c77975b4ccce940d41933081b19d7.png
│ │ └── Contents.json
├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
├── Info.plist
├── UIKit+Extensions.swift
├── ViewController.swift
└── file.txt
├── LICENSE
├── Package.swift
├── README.md
├── Sources
└── SwiftRichString
│ ├── Attributes
│ ├── ColorConvertible.swift
│ ├── CommonsAttributes.swift
│ ├── DynamicText.swift
│ ├── FontConvertible.swift
│ ├── FontData.swift
│ ├── FontInfoAttribute.swift
│ ├── TextTransform.swift
│ └── URLRepresentable.swift
│ ├── Extensions
│ ├── AttributedString+Attachments.swift
│ ├── AttributedString+Extension.swift
│ ├── AttributedString+FunctionBuilder.swift
│ ├── String+Ext.swift
│ ├── String+Subscript.swift
│ ├── SystemFonts.swift
│ └── UIKit+Extras.swift
│ ├── Style
│ ├── Style.swift
│ ├── StyleGroup.swift
│ ├── StyleProtocol.swift
│ ├── StyleRegEx.swift
│ └── StylesManager.swift
│ └── Support
│ ├── AssociatedValues.swift
│ ├── AsyncTextAttachment.swift
│ ├── Compatibility.swift
│ ├── Extensions.swift
│ ├── OrderedDictionary.swift
│ ├── XMLDynamicAttributesResolver.swift
│ └── XMLStringBuilder.swift
├── SwiftRichString.playground
├── Contents.swift
├── Sources
│ └── AttributedStringController.swift
└── contents.xcplayground
├── SwiftRichString.podspec
├── SwiftRichString.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ ├── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
│ └── xcuserdata
│ │ └── daniele.xcuserdatad
│ │ └── UserInterfaceState.xcuserstate
├── xcshareddata
│ └── xcschemes
│ │ ├── SwiftRichString-iOS.xcscheme
│ │ ├── SwiftRichString-macOS.xcscheme
│ │ ├── SwiftRichString-tvOS.xcscheme
│ │ └── SwiftRichString-watchOS.xcscheme
└── xcuserdata
│ └── daniele.xcuserdatad
│ ├── xcdebugger
│ └── Breakpoints_v2.xcbkptlist
│ └── xcschemes
│ └── xcschememanagement.plist
├── SwiftRichString.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── Tests
├── LinuxMain.swift
└── SwiftRichStringTests
│ └── SwiftRichStringTests.swift
└── banner.png
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: malcommac
4 | custom: https://www.paypal.com/paypalme2/danielemargutti
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # macOS
2 | .DS_Store
3 |
4 | # Xcode
5 | xcuserdata
6 | *.xcuserstate
7 | *.xctimeline
8 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ## CHANGELOG
6 |
7 | * Version **[2.0.1](#201)**
8 |
9 | * Version **[1.1.0](#101)** (for Swift 4)
10 | * Version **[1.0.1](#101)** (for Swift 4)
11 | * Version **[0.9.10](#0910)** (Latest Swift 3.x compatible version)
12 | * Version **[0.9.9](#099)**
13 | * Version **[0.9.8](#097)**
14 | * Version **[0.9.5](#095)**
15 |
16 |
17 |
18 | ## SwiftRichString 2.0.1
19 | ---
20 | - **Release Date**: 2018-05-20
21 | - **Zipped Version**: [Download 2.0.1](https://github.com/malcommac/SwiftRichString/releases/tag/2.0.1)
22 |
23 | This is a total rewrite of the library to simplify and consolidate the APIs functionalities. See documentation in README for detailed informations.
24 |
25 |
26 |
27 |
28 | ## SwiftRichString 1.1.0
29 | ---
30 | - **Release Date**: 2017-12-28
31 | - **Zipped Version**: [Download 1.1.0](https://github.com/malcommac/SwiftRichString/releases/tag/1.1.0)
32 |
33 | * [#26](https://github.com/malcommac/SwiftRichString/issues/26) Fixed an issue when parsing ` ` in `MarkupString` class
34 | * [#29](https://github.com/malcommac/SwiftRichString/issues/29) Fixed several warnings coming to Swift 4
35 |
36 |
37 |
38 | ## SwiftRichString 1.0.1
39 | ---
40 | - **Release Date**: 2017-09-14
41 | - **Zipped Version**: [Download 1.0.0](https://github.com/malcommac/SwiftRichString/releases/tag/1.0.0)
42 |
43 | This is the first version compatible with Swift 4.
44 |
45 |
46 |
47 | ## SwiftRichString 0.9.10
48 | ---
49 | - **Release Date**: 2017-09-18
50 | - **Zipped Version**: [Download 0.9.10](https://github.com/malcommac/SwiftRichString/releases/tag/0.9.10)
51 |
52 | Fix minor issue compiling with Xcode 9 and Swift 3.2
53 |
54 |
55 |
56 | ## SwiftRichString 0.9.9
57 | ---
58 | - **Release Date**: 2017-07-06
59 | - **Zipped Version**: [Download 0.9.9](https://github.com/malcommac/SwiftRichString/releases/tag/0.9.8)
60 |
61 | - [#18](https://github.com/malcommac/SwiftRichString/issues/18) Added `renderTags(withStyles:)` func in `String` extension. It will a shortcut to parse an html-tagged string and return the `NSMutableAttributedString` instance.
62 | - [#19](https://github.com/malcommac/SwiftRichString/issues/19) `MarkupString` classes does not `throws` anymore; when parsing fails to invalid strings it will return `nil`.
63 | - [#5](https://github.com/malcommac/SwiftRichString/issues/5) A new function is added to parse multiple regular expressions and apply to each one one or more styles. It's called `func set(regExpStyles: [RegExpPatternStyles], default dStyle: Style? = nil) -> NSMutableAttributedString` and accepts an array of `RegExpPatternStyles` structs (which defines the regexp rule, options and and array of `Style` to apply on match). `default` parameter allows you to set a default style to apply before rules are evaluated.
64 | - [#2](https://github.com/malcommac/SwiftRichString/issues/2) Resolved an issue with CocoaPods
65 | - [#20](https://github.com/malcommac/SwiftRichString/issues/20) Added compatibility with `watchOS`, `tvOS` and `macOS`.
66 |
67 |
68 |
69 |
70 | ## SwiftRichString 0.9.8
71 | ---
72 | - **Release Date**: 2017-04-02
73 | - **Zipped Version**: [Download 0.9.8](https://github.com/malcommac/SwiftRichString/releases/tag/0.9.8)
74 |
75 | - [#4] Added `set(stylesArray:)` with func in `String` extension
76 |
77 |
78 |
79 | ## SwiftRichString 0.9.5
80 | ---
81 | - **Release Date**: 2017-03-01
82 | - **Zipped Version**: [Download 0.9.5](https://github.com/malcommac/SwiftRichString/releases/tag/0.9.5)
83 |
84 | - [#4](https://github.com/malcommac/SwiftRichString/pull/4) Added support for `.writingDirection`
85 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, gender identity and expression, level of experience,
9 | nationality, personal appearance, race, religion, or sexual identity and
10 | orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at . All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 |
63 | Further details of specific enforcement policies may be posted separately.
64 |
65 | Project maintainers who do not follow or enforce the Code of Conduct in good
66 | faith may face temporary or permanent repercussions as determined by other
67 | members of the project's leadership.
68 |
69 | ## Attribution
70 |
71 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
72 | available at [http://contributor-covenant.org/version/1/4][version]
73 |
74 | [homepage]: http://contributor-covenant.org
75 | [version]: http://contributor-covenant.org/version/1/4/
--------------------------------------------------------------------------------
/Configs/SwiftRichString.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 | $(MARKETING_VERSION)
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSHumanReadableCopyright
24 | Copyright © 2018 Daniele Margutti. All rights reserved.
25 | NSPrincipalClass
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/Configs/SwiftRichStringTests.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/DemoApp/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // DemoApp
4 | //
5 | // Created by Daniele Margutti on 21/03/2018.
6 | // Copyright © 2018 SwiftRichString. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
18 | // Override point for customization after application launch.
19 | return true
20 | }
21 |
22 | func applicationWillResignActive(_ application: UIApplication) {
23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
25 | }
26 |
27 | func applicationDidEnterBackground(_ application: UIApplication) {
28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
30 | }
31 |
32 | func applicationWillEnterForeground(_ application: UIApplication) {
33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
34 | }
35 |
36 | func applicationDidBecomeActive(_ application: UIApplication) {
37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
38 | }
39 |
40 | func applicationWillTerminate(_ application: UIApplication) {
41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
42 | }
43 |
44 |
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/DemoApp/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 | }
--------------------------------------------------------------------------------
/DemoApp/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 |
--------------------------------------------------------------------------------
/DemoApp/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 |
--------------------------------------------------------------------------------
/DemoApp/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | 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 |
--------------------------------------------------------------------------------
/DemoApp/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // DemoApp
4 | //
5 | // Created by Daniele Margutti on 21/03/2018.
6 | // Copyright © 2018 SwiftRichString. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ViewController: UIViewController {
12 |
13 | @IBOutlet public var label: UILabel?
14 |
15 | override func viewDidLoad() {
16 | super.viewDidLoad()
17 | // Do any additional setup after loading the view, typically from a nib.
18 | }
19 |
20 | override func didReceiveMemoryWarning() {
21 | super.didReceiveMemoryWarning()
22 | // Dispose of any resources that can be recreated.
23 | }
24 |
25 |
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/Documentation_Assests/Logo.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/malcommac/SwiftRichString/e0b72d5c96968d7802856d2be096202c9798e8d1/Documentation_Assests/Logo.sketch
--------------------------------------------------------------------------------
/Documentation_Assests/image_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/malcommac/SwiftRichString/e0b72d5c96968d7802856d2be096202c9798e8d1/Documentation_Assests/image_1.png
--------------------------------------------------------------------------------
/Documentation_Assests/image_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/malcommac/SwiftRichString/e0b72d5c96968d7802856d2be096202c9798e8d1/Documentation_Assests/image_2.png
--------------------------------------------------------------------------------
/Documentation_Assests/image_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/malcommac/SwiftRichString/e0b72d5c96968d7802856d2be096202c9798e8d1/Documentation_Assests/image_3.png
--------------------------------------------------------------------------------
/Documentation_Assests/image_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/malcommac/SwiftRichString/e0b72d5c96968d7802856d2be096202c9798e8d1/Documentation_Assests/image_4.png
--------------------------------------------------------------------------------
/Documentation_Assests/image_5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/malcommac/SwiftRichString/e0b72d5c96968d7802856d2be096202c9798e8d1/Documentation_Assests/image_5.png
--------------------------------------------------------------------------------
/Documentation_Assests/image_6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/malcommac/SwiftRichString/e0b72d5c96968d7802856d2be096202c9798e8d1/Documentation_Assests/image_6.png
--------------------------------------------------------------------------------
/ExampleMac/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // ExampleMac
4 | //
5 | // Created by Daniele Margutti on 05/05/2018.
6 | // Copyright © 2018 SwiftRichString. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | @NSApplicationMain
12 | class AppDelegate: NSObject, NSApplicationDelegate {
13 |
14 |
15 |
16 | func applicationDidFinishLaunching(_ aNotification: Notification) {
17 | // Insert code here to initialize your application
18 | }
19 |
20 | func applicationWillTerminate(_ aNotification: Notification) {
21 | // Insert code here to tear down your application
22 | }
23 |
24 |
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/ExampleMac/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "mac",
5 | "size" : "16x16",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "mac",
10 | "size" : "16x16",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "mac",
15 | "size" : "32x32",
16 | "scale" : "1x"
17 | },
18 | {
19 | "idiom" : "mac",
20 | "size" : "32x32",
21 | "scale" : "2x"
22 | },
23 | {
24 | "idiom" : "mac",
25 | "size" : "128x128",
26 | "scale" : "1x"
27 | },
28 | {
29 | "idiom" : "mac",
30 | "size" : "128x128",
31 | "scale" : "2x"
32 | },
33 | {
34 | "idiom" : "mac",
35 | "size" : "256x256",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "mac",
40 | "size" : "256x256",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "mac",
45 | "size" : "512x512",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "mac",
50 | "size" : "512x512",
51 | "scale" : "2x"
52 | }
53 | ],
54 | "info" : {
55 | "version" : 1,
56 | "author" : "xcode"
57 | }
58 | }
--------------------------------------------------------------------------------
/ExampleMac/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/ExampleMac/ExampleMac.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.files.user-selected.read-only
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/ExampleMac/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleVersion
22 | 1
23 | LSMinimumSystemVersion
24 | $(MACOSX_DEPLOYMENT_TARGET)
25 | NSHumanReadableCopyright
26 | Copyright © 2018 SwiftRichString. All rights reserved.
27 | NSMainStoryboardFile
28 | Main
29 | NSPrincipalClass
30 | NSApplication
31 |
32 |
33 |
--------------------------------------------------------------------------------
/ExampleMac/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // ExampleMac
4 | //
5 | // Created by Daniele Margutti on 05/05/2018.
6 | // Copyright © 2018 SwiftRichString. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | class ViewController: NSViewController {
12 |
13 | override var representedObject: Any? {
14 | didSet {
15 | // Update the view, if already loaded.
16 | }
17 | }
18 |
19 |
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/ExampleTvOS/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // ExampleTvOS
4 | //
5 | // Created by Daniele Margutti on 20/05/2018.
6 | // Copyright © 2018 SwiftRichString. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
18 | // Override point for customization after application launch.
19 | return true
20 | }
21 |
22 | func applicationWillResignActive(_ application: UIApplication) {
23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
24 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
25 | }
26 |
27 | func applicationDidEnterBackground(_ application: UIApplication) {
28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
30 | }
31 |
32 | func applicationWillEnterForeground(_ application: UIApplication) {
33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
34 | }
35 |
36 | func applicationDidBecomeActive(_ application: UIApplication) {
37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
38 | }
39 |
40 | func applicationWillTerminate(_ application: UIApplication) {
41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
42 | }
43 |
44 |
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/ExampleTvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv"
5 | }
6 | ],
7 | "info" : {
8 | "version" : 1,
9 | "author" : "xcode"
10 | }
11 | }
--------------------------------------------------------------------------------
/ExampleTvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Back.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/ExampleTvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "layers" : [
3 | {
4 | "filename" : "Front.imagestacklayer"
5 | },
6 | {
7 | "filename" : "Middle.imagestacklayer"
8 | },
9 | {
10 | "filename" : "Back.imagestacklayer"
11 | }
12 | ],
13 | "info" : {
14 | "version" : 1,
15 | "author" : "xcode"
16 | }
17 | }
--------------------------------------------------------------------------------
/ExampleTvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv"
5 | }
6 | ],
7 | "info" : {
8 | "version" : 1,
9 | "author" : "xcode"
10 | }
11 | }
--------------------------------------------------------------------------------
/ExampleTvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Front.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/ExampleTvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv"
5 | }
6 | ],
7 | "info" : {
8 | "version" : 1,
9 | "author" : "xcode"
10 | }
11 | }
--------------------------------------------------------------------------------
/ExampleTvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Middle.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/ExampleTvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | }
11 | ],
12 | "info" : {
13 | "version" : 1,
14 | "author" : "xcode"
15 | }
16 | }
--------------------------------------------------------------------------------
/ExampleTvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Back.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/ExampleTvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "layers" : [
3 | {
4 | "filename" : "Front.imagestacklayer"
5 | },
6 | {
7 | "filename" : "Middle.imagestacklayer"
8 | },
9 | {
10 | "filename" : "Back.imagestacklayer"
11 | }
12 | ],
13 | "info" : {
14 | "version" : 1,
15 | "author" : "xcode"
16 | }
17 | }
--------------------------------------------------------------------------------
/ExampleTvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | }
11 | ],
12 | "info" : {
13 | "version" : 1,
14 | "author" : "xcode"
15 | }
16 | }
--------------------------------------------------------------------------------
/ExampleTvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Front.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/ExampleTvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Content.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | }
11 | ],
12 | "info" : {
13 | "version" : 1,
14 | "author" : "xcode"
15 | }
16 | }
--------------------------------------------------------------------------------
/ExampleTvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/App Icon.imagestack/Middle.imagestacklayer/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/ExampleTvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "assets" : [
3 | {
4 | "size" : "1280x768",
5 | "idiom" : "tv",
6 | "filename" : "App Icon - App Store.imagestack",
7 | "role" : "primary-app-icon"
8 | },
9 | {
10 | "size" : "400x240",
11 | "idiom" : "tv",
12 | "filename" : "App Icon.imagestack",
13 | "role" : "primary-app-icon"
14 | },
15 | {
16 | "size" : "2320x720",
17 | "idiom" : "tv",
18 | "filename" : "Top Shelf Image Wide.imageset",
19 | "role" : "top-shelf-image-wide"
20 | },
21 | {
22 | "size" : "1920x720",
23 | "idiom" : "tv",
24 | "filename" : "Top Shelf Image.imageset",
25 | "role" : "top-shelf-image"
26 | }
27 | ],
28 | "info" : {
29 | "version" : 1,
30 | "author" : "xcode"
31 | }
32 | }
--------------------------------------------------------------------------------
/ExampleTvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image Wide.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | }
11 | ],
12 | "info" : {
13 | "version" : 1,
14 | "author" : "xcode"
15 | }
16 | }
--------------------------------------------------------------------------------
/ExampleTvOS/Assets.xcassets/App Icon & Top Shelf Image.brandassets/Top Shelf Image.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "tv",
5 | "scale" : "1x"
6 | },
7 | {
8 | "idiom" : "tv",
9 | "scale" : "2x"
10 | }
11 | ],
12 | "info" : {
13 | "version" : 1,
14 | "author" : "xcode"
15 | }
16 | }
--------------------------------------------------------------------------------
/ExampleTvOS/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/ExampleTvOS/Assets.xcassets/Launch Image.launchimage/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "orientation" : "landscape",
5 | "idiom" : "tv",
6 | "extent" : "full-screen",
7 | "minimum-system-version" : "11.0",
8 | "scale" : "2x"
9 | },
10 | {
11 | "orientation" : "landscape",
12 | "idiom" : "tv",
13 | "extent" : "full-screen",
14 | "minimum-system-version" : "9.0",
15 | "scale" : "1x"
16 | }
17 | ],
18 | "info" : {
19 | "version" : 1,
20 | "author" : "xcode"
21 | }
22 | }
--------------------------------------------------------------------------------
/ExampleTvOS/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 |
--------------------------------------------------------------------------------
/ExampleTvOS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIMainStoryboardFile
24 | Main
25 | UIRequiredDeviceCapabilities
26 |
27 | arm64
28 |
29 | UIUserInterfaceStyle
30 | Automatic
31 |
32 |
33 |
--------------------------------------------------------------------------------
/ExampleTvOS/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // ExampleTvOS
4 | //
5 | // Created by Daniele Margutti on 20/05/2018.
6 | // Copyright © 2018 SwiftRichString. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ViewController: UIViewController {
12 |
13 | override func viewDidLoad() {
14 | super.viewDidLoad()
15 | // Do any additional setup after loading the view, typically from a nib.
16 | }
17 |
18 | override func didReceiveMemoryWarning() {
19 | super.didReceiveMemoryWarning()
20 | // Dispose of any resources that can be recreated.
21 | }
22 |
23 |
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/ExampleWatchOS Extension/Assets.xcassets/Complication.complicationset/Circular.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "watch",
5 | "scale" : "2x",
6 | "screen-width" : "<=145"
7 | },
8 | {
9 | "idiom" : "watch",
10 | "scale" : "2x",
11 | "screen-width" : ">145"
12 | }
13 | ],
14 | "info" : {
15 | "version" : 1,
16 | "author" : "xcode"
17 | }
18 | }
--------------------------------------------------------------------------------
/ExampleWatchOS Extension/Assets.xcassets/Complication.complicationset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "assets" : [
3 | {
4 | "idiom" : "watch",
5 | "filename" : "Circular.imageset",
6 | "role" : "circular"
7 | },
8 | {
9 | "idiom" : "watch",
10 | "filename" : "Extra Large.imageset",
11 | "role" : "extra-large"
12 | },
13 | {
14 | "idiom" : "watch",
15 | "filename" : "Modular.imageset",
16 | "role" : "modular"
17 | },
18 | {
19 | "idiom" : "watch",
20 | "filename" : "Utilitarian.imageset",
21 | "role" : "utilitarian"
22 | }
23 | ],
24 | "info" : {
25 | "version" : 1,
26 | "author" : "xcode"
27 | }
28 | }
--------------------------------------------------------------------------------
/ExampleWatchOS Extension/Assets.xcassets/Complication.complicationset/Extra Large.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "watch",
5 | "scale" : "2x",
6 | "screen-width" : "<=145"
7 | },
8 | {
9 | "idiom" : "watch",
10 | "scale" : "2x",
11 | "screen-width" : ">145"
12 | }
13 | ],
14 | "info" : {
15 | "version" : 1,
16 | "author" : "xcode"
17 | }
18 | }
--------------------------------------------------------------------------------
/ExampleWatchOS Extension/Assets.xcassets/Complication.complicationset/Modular.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "watch",
5 | "scale" : "2x",
6 | "screen-width" : "<=145"
7 | },
8 | {
9 | "idiom" : "watch",
10 | "scale" : "2x",
11 | "screen-width" : ">145"
12 | }
13 | ],
14 | "info" : {
15 | "version" : 1,
16 | "author" : "xcode"
17 | }
18 | }
--------------------------------------------------------------------------------
/ExampleWatchOS Extension/Assets.xcassets/Complication.complicationset/Utilitarian.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "watch",
5 | "scale" : "2x",
6 | "screen-width" : "<=145"
7 | },
8 | {
9 | "idiom" : "watch",
10 | "scale" : "2x",
11 | "screen-width" : ">145"
12 | }
13 | ],
14 | "info" : {
15 | "version" : 1,
16 | "author" : "xcode"
17 | }
18 | }
--------------------------------------------------------------------------------
/ExampleWatchOS Extension/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/ExampleWatchOS Extension/ExtensionDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ExtensionDelegate.swift
3 | // ExampleWatchOS Extension
4 | //
5 | // Created by Daniele Margutti on 20/05/2018.
6 | // Copyright © 2018 SwiftRichString. All rights reserved.
7 | //
8 |
9 | import WatchKit
10 |
11 | class ExtensionDelegate: NSObject, WKExtensionDelegate {
12 |
13 | func applicationDidFinishLaunching() {
14 | // Perform any final initialization of your application.
15 | }
16 |
17 | func applicationDidBecomeActive() {
18 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
19 | }
20 |
21 | func applicationWillResignActive() {
22 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
23 | // Use this method to pause ongoing tasks, disable timers, etc.
24 | }
25 |
26 | func handle(_ backgroundTasks: Set) {
27 | // Sent when the system needs to launch the application in the background to process tasks. Tasks arrive in a set, so loop through and process each one.
28 | for task in backgroundTasks {
29 | // Use a switch statement to check the task type
30 | switch task {
31 | case let backgroundTask as WKApplicationRefreshBackgroundTask:
32 | // Be sure to complete the background task once you’re done.
33 | backgroundTask.setTaskCompletedWithSnapshot(false)
34 | case let snapshotTask as WKSnapshotRefreshBackgroundTask:
35 | // Snapshot tasks have a unique completion call, make sure to set your expiration date
36 | snapshotTask.setTaskCompleted(restoredDefaultState: true, estimatedSnapshotExpiration: Date.distantFuture, userInfo: nil)
37 | case let connectivityTask as WKWatchConnectivityRefreshBackgroundTask:
38 | // Be sure to complete the connectivity task once you’re done.
39 | connectivityTask.setTaskCompletedWithSnapshot(false)
40 | case let urlSessionTask as WKURLSessionRefreshBackgroundTask:
41 | // Be sure to complete the URL session task once you’re done.
42 | urlSessionTask.setTaskCompletedWithSnapshot(false)
43 | default:
44 | // make sure to complete unhandled task types
45 | task.setTaskCompletedWithSnapshot(false)
46 | }
47 | }
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/ExampleWatchOS Extension/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | ExampleWatchOS Extension
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | XPC!
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleVersion
22 | 1
23 | NSExtension
24 |
25 | NSExtensionAttributes
26 |
27 | WKAppBundleIdentifier
28 | com.swiftrichstring.ExampleiOS.watchkitapp
29 |
30 | NSExtensionPointIdentifier
31 | com.apple.watchkit
32 |
33 | WKExtensionDelegateClassName
34 | $(PRODUCT_MODULE_NAME).ExtensionDelegate
35 |
36 |
37 |
--------------------------------------------------------------------------------
/ExampleWatchOS Extension/InterfaceController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // InterfaceController.swift
3 | // ExampleWatchOS Extension
4 | //
5 | // Created by Daniele Margutti on 20/05/2018.
6 | // Copyright © 2018 SwiftRichString. All rights reserved.
7 | //
8 |
9 | import WatchKit
10 | import Foundation
11 |
12 |
13 | class InterfaceController: WKInterfaceController {
14 |
15 | override func awake(withContext context: Any?) {
16 | super.awake(withContext: context)
17 |
18 | // Configure interface objects here.
19 | }
20 |
21 | override func willActivate() {
22 | // This method is called when watch view controller is about to be visible to user
23 | super.willActivate()
24 | }
25 |
26 | override func didDeactivate() {
27 | // This method is called when watch view controller is no longer visible
28 | super.didDeactivate()
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/ExampleWatchOS Extension/NotificationController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NotificationController.swift
3 | // ExampleWatchOS Extension
4 | //
5 | // Created by Daniele Margutti on 20/05/2018.
6 | // Copyright © 2018 SwiftRichString. All rights reserved.
7 | //
8 |
9 | import WatchKit
10 | import Foundation
11 | import UserNotifications
12 |
13 |
14 | class NotificationController: WKUserNotificationInterfaceController {
15 |
16 | override init() {
17 | // Initialize variables here.
18 | super.init()
19 |
20 | // Configure interface objects here.
21 | }
22 |
23 | override func willActivate() {
24 | // This method is called when watch view controller is about to be visible to user
25 | super.willActivate()
26 | }
27 |
28 | override func didDeactivate() {
29 | // This method is called when watch view controller is no longer visible
30 | super.didDeactivate()
31 | }
32 |
33 | /*
34 | override func didReceive(_ notification: UNNotification, withCompletion completionHandler: @escaping (WKUserNotificationInterfaceType) -> Swift.Void) {
35 | // This method is called when a notification needs to be presented.
36 | // Implement it if you use a dynamic notification interface.
37 | // Populate your dynamic notification interface as quickly as possible.
38 | //
39 | // After populating your dynamic notification interface call the completion block.
40 | completionHandler(.custom)
41 | }
42 | */
43 | }
44 |
--------------------------------------------------------------------------------
/ExampleWatchOS Extension/PushNotificationPayload.apns:
--------------------------------------------------------------------------------
1 | {
2 | "aps": {
3 | "alert": {
4 | "body": "Test message",
5 | "title": "Optional title"
6 | },
7 | "category": "myCategory",
8 | "thread-id":"5280"
9 | },
10 |
11 | "WatchKit Simulator Actions": [
12 | {
13 | "title": "First Button",
14 | "identifier": "firstButtonAction"
15 | }
16 | ],
17 |
18 | "customKey": "Use this file to define a testing payload for your notifications. The aps dictionary specifies the category, alert text and title. The WatchKit Simulator Actions array can provide info for one or more action buttons in addition to the standard Dismiss button. Any other top level keys are custom payload. If you have multiple such JSON files in your project, you'll be able to select them when choosing to debug the notification interface of your Watch App."
19 | }
20 |
--------------------------------------------------------------------------------
/ExampleWatchOS/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "24x24",
5 | "idiom" : "watch",
6 | "scale" : "2x",
7 | "role" : "notificationCenter",
8 | "subtype" : "38mm"
9 | },
10 | {
11 | "size" : "27.5x27.5",
12 | "idiom" : "watch",
13 | "scale" : "2x",
14 | "role" : "notificationCenter",
15 | "subtype" : "42mm"
16 | },
17 | {
18 | "size" : "29x29",
19 | "idiom" : "watch",
20 | "role" : "companionSettings",
21 | "scale" : "2x"
22 | },
23 | {
24 | "size" : "29x29",
25 | "idiom" : "watch",
26 | "role" : "companionSettings",
27 | "scale" : "3x"
28 | },
29 | {
30 | "size" : "40x40",
31 | "idiom" : "watch",
32 | "scale" : "2x",
33 | "role" : "appLauncher",
34 | "subtype" : "38mm"
35 | },
36 | {
37 | "size" : "44x44",
38 | "idiom" : "watch",
39 | "scale" : "2x",
40 | "role" : "appLauncher",
41 | "subtype" : "40mm"
42 | },
43 | {
44 | "size" : "50x50",
45 | "idiom" : "watch",
46 | "scale" : "2x",
47 | "role" : "appLauncher",
48 | "subtype" : "44mm"
49 | },
50 | {
51 | "size" : "86x86",
52 | "idiom" : "watch",
53 | "scale" : "2x",
54 | "role" : "quickLook",
55 | "subtype" : "38mm"
56 | },
57 | {
58 | "size" : "98x98",
59 | "idiom" : "watch",
60 | "scale" : "2x",
61 | "role" : "quickLook",
62 | "subtype" : "42mm"
63 | },
64 | {
65 | "size" : "108x108",
66 | "idiom" : "watch",
67 | "scale" : "2x",
68 | "role" : "quickLook",
69 | "subtype" : "44mm"
70 | },
71 | {
72 | "idiom" : "watch-marketing",
73 | "size" : "1024x1024",
74 | "scale" : "1x"
75 | }
76 | ],
77 | "info" : {
78 | "version" : 1,
79 | "author" : "xcode"
80 | }
81 | }
--------------------------------------------------------------------------------
/ExampleWatchOS/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/ExampleWatchOS/Base.lproj/Interface.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 |
--------------------------------------------------------------------------------
/ExampleWatchOS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | ExampleiOS
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleVersion
22 | 1
23 | UISupportedInterfaceOrientations
24 |
25 | UIInterfaceOrientationPortrait
26 | UIInterfaceOrientationPortraitUpsideDown
27 |
28 | WKCompanionAppBundleIdentifier
29 | com.swiftrichstring.ExampleiOS
30 | WKWatchKitApp
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/ExampleiOS/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // ExampleiOS
4 | //
5 | // Created by Daniele Margutti on 05/05/2018.
6 | // Copyright © 2018 SwiftRichString. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
18 | // Override point for customization after application launch.
19 | return true
20 | }
21 |
22 | func applicationWillResignActive(_ application: UIApplication) {
23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
25 | }
26 |
27 | func applicationDidEnterBackground(_ application: UIApplication) {
28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
30 | }
31 |
32 | func applicationWillEnterForeground(_ application: UIApplication) {
33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
34 | }
35 |
36 | func applicationDidBecomeActive(_ application: UIApplication) {
37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
38 | }
39 |
40 | func applicationWillTerminate(_ application: UIApplication) {
41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
42 | }
43 |
44 |
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/ExampleiOS/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/ExampleiOS/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/ExampleiOS/Assets.xcassets/therocket.imageset/816c77975b4ccce940d41933081b19d7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/malcommac/SwiftRichString/e0b72d5c96968d7802856d2be096202c9798e8d1/ExampleiOS/Assets.xcassets/therocket.imageset/816c77975b4ccce940d41933081b19d7.png
--------------------------------------------------------------------------------
/ExampleiOS/Assets.xcassets/therocket.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "816c77975b4ccce940d41933081b19d7.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "version" : 1,
19 | "author" : "xcode"
20 | }
21 | }
--------------------------------------------------------------------------------
/ExampleiOS/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 |
--------------------------------------------------------------------------------
/ExampleiOS/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 | Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda.
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/ExampleiOS/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | NSAppTransportSecurity
24 |
25 | NSAllowsArbitraryLoads
26 |
27 |
28 | UILaunchStoryboardName
29 | LaunchScreen
30 | UIMainStoryboardFile
31 | Main
32 | UIRequiredDeviceCapabilities
33 |
34 | armv7
35 |
36 | UISupportedInterfaceOrientations
37 |
38 | UIInterfaceOrientationPortrait
39 | UIInterfaceOrientationLandscapeLeft
40 | UIInterfaceOrientationLandscapeRight
41 |
42 | UISupportedInterfaceOrientations~ipad
43 |
44 | UIInterfaceOrientationPortrait
45 | UIInterfaceOrientationPortraitUpsideDown
46 | UIInterfaceOrientationLandscapeLeft
47 | UIInterfaceOrientationLandscapeRight
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/ExampleiOS/UIKit+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UIKit+Extensions.swift
3 | // ExampleiOS
4 | //
5 | // Created by daniele on 21/12/2019.
6 | // Copyright © 2019 SwiftRichString. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIColor {
12 |
13 | public static func randomColors(_ count: Int) -> [UIColor] {
14 | return (0.. UIColor in
15 | randomColor()
16 | }
17 | }
18 |
19 | public static func randomColor() -> UIColor {
20 | let redValue = CGFloat.random(in: 0...1)
21 | let greenValue = CGFloat.random(in: 0...1)
22 | let blueValue = CGFloat.random(in: 0...1)
23 |
24 | let randomColor = UIColor(red: redValue, green: greenValue, blue: blueValue, alpha: 1.0)
25 | return randomColor
26 | }
27 |
28 | }
29 |
30 | extension UIFont {
31 |
32 | /// Return the same font with given weight.
33 | ///
34 | /// - Parameter weight: weight you want to get
35 | public func withWeight(_ weight: UIFont.Weight) -> UIFont {
36 | let descriptor = fontDescriptor.addingAttributes([UIFontDescriptor.AttributeName.traits: [UIFontDescriptor.TraitKey.weight: weight]])
37 | return UIFont(descriptor: descriptor, size: 0) // size 0 means keep the size as it is
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/ExampleiOS/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // ExampleiOS
4 | //
5 | // Created by Daniele Margutti on 05/05/2018.
6 | // Copyright © 2018 SwiftRichString. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ViewController: UIViewController {
12 |
13 | @IBOutlet public var textView: UITextView?
14 |
15 | let baseFontSize: CGFloat = 16
16 |
17 | override func viewDidLoad() {
18 | super.viewDidLoad()
19 |
20 | // let text = """
21 | //- Performed!
22 | //"""
23 | // let base = Style {
24 | // $0.font = UIFont.boldSystemFont(ofSize: 14)
25 | // $0.color = UIColor(hexString: "#8E8E8E")
26 | // }
27 | //
28 | // let xmlStyle = StyleXML(base: base)
29 | // xmlStyle.imageProvider = { imageName, attributes in
30 | // fatalError()
31 | // }
32 | //
33 | // self.textView?.attributedText = text.set(style: xmlStyle)
34 |
35 | // return
36 |
37 | // self.textView?.attributedText = "ciao ciao " + AttributedString(image: UIImage(named: "rocket")!,
38 | // bounds: CGRect(x: 0, y: -20, width: 25, height: 25)) + "ciao ciao"
39 | //
40 | //
41 | // return
42 | //
43 | let bodyHTML = try! String(contentsOfFile: Bundle.main.path(forResource: "file", ofType: "txt")!)
44 |
45 | // Create a set of styles
46 |
47 | let headerStyle = Style {
48 | $0.font = UIFont.boldSystemFont(ofSize: self.baseFontSize * 1.15)
49 | $0.lineSpacing = 1
50 | $0.kerning = Kerning.adobe(-20)
51 | }
52 | let boldStyle = Style {
53 | $0.font = UIFont.boldSystemFont(ofSize: self.baseFontSize)
54 | if #available(iOS 11.0, *) {
55 | $0.dynamicText = DynamicText {
56 | $0.style = .body
57 | $0.maximumSize = 35.0
58 | $0.traitCollection = UITraitCollection(userInterfaceIdiom: .phone)
59 | }
60 | }
61 | }
62 | let italicStyle = Style {
63 | $0.font = UIFont.italicSystemFont(ofSize: self.baseFontSize)
64 | }
65 |
66 | let uppercasedRed = Style {
67 | $0.font = UIFont.italicSystemFont(ofSize: self.baseFontSize)
68 | $0.color = UIColor.red
69 | $0.textTransforms = [
70 | .uppercase
71 | ]
72 | }
73 |
74 | // And a group of them
75 | let styleGroup = StyleGroup(base: Style {
76 | $0.font = UIFont.systemFont(ofSize: self.baseFontSize)
77 | $0.lineSpacing = 2
78 | $0.kerning = Kerning.adobe(-15)
79 | }, [
80 | "ur": uppercasedRed,
81 | "h3": headerStyle,
82 | "h4": headerStyle,
83 | "h5": headerStyle,
84 | "strong": boldStyle,
85 | "b": boldStyle,
86 | "em": italicStyle,
87 | "i": italicStyle,
88 | "a": uppercasedRed,
89 | "li": Style {
90 | $0.paragraphSpacingBefore = self.baseFontSize / 2
91 | $0.firstLineHeadIndent = self.baseFontSize
92 | $0.headIndent = self.baseFontSize * 1.71
93 | },
94 | "sup": Style {
95 | $0.font = UIFont.systemFont(ofSize: self.baseFontSize / 1.2)
96 | $0.baselineOffset = Float(self.baseFontSize) / 3.5
97 | }])
98 |
99 | // Apply a custom xml attribute resolver
100 | styleGroup.xmlAttributesResolver = MyXMLDynamicAttributesResolver()
101 |
102 | // Render
103 | self.textView?.attributedText = bodyHTML.set(style: styleGroup)
104 |
105 | // Accessibility support
106 | if #available(iOS 10.0, *) {
107 | self.textView?.adjustsFontForContentSizeCategory = true
108 | }
109 |
110 | }
111 | }
112 |
113 | public class MyXMLDynamicAttributesResolver: StandardXMLAttributesResolver {
114 |
115 | public override func styleForUnknownXMLTag(_ tag: String, to attributedString: inout AttributedString, attributes: [String : String]?, fromStyle forStyle: StyleXML) {
116 | super.styleForUnknownXMLTag(tag, to: &attributedString, attributes: attributes, fromStyle: forStyle)
117 |
118 | if tag == "rainbow" {
119 | let colors = UIColor.randomColors(attributedString.length)
120 | for i in 0..Parler du don d'organe n'est plus tabou. Je me renseigne, j'en discute avec mes proches,... et je décide!
2 |
3 |
4 |
5 |
6 |
7 | En Belgique , au début des années 2000, le nombre de donneurs d’organes avait tendance à diminuer et les listes d’attente à augmenter considérablement ayant comme corollaire une augmentation de la mortalité des patients inscrits sur les listes d’attente.
8 |
9 | Soucieux de cette situation, en juin 2005, le Ministre en charge de la Santé publique a souhaité mettre sur pied une vaste campagne de sensibilisation entièrement dédiée au don d’organes.
10 |
11 | Depuis, de nombreuses actions entreprises par le SPF Santé publique viennent renforcer toutes celles qui sont accomplies au quotidien par les coordinateurs de transplantation, les coordinateurs locaux de don, les associations de familles de donneurs, les associations de patients transplantés et ce, depuis de très nombreuses années.
12 |
13 | Objectifs poursuivis
14 |
15 | Les objectifs principaux de cette campagne sont:
16 | • d’améliorer la sensibilisation au don auprès des différents groupes auxquels les messages s’adressent,
17 | • d’inviter les citoyens à «oser» en parler en famille, entre amis, entre collègues. Aborder la mort – sa propre mort – reste relativement tabou pour beaucoup.
18 |
19 | Pour en savoir plus... www.health.belgium.be/fr/sante/prenez-soin-de-vous/debut-et-fin-de-vie/don-dorganes
20 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2018 Daniele Margutti
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/Package.swift:
--------------------------------------------------------------------------------
1 | // swift-tools-version:5.1
2 | // The swift-tools-version declares the minimum version of Swift required to build this package.
3 |
4 | import PackageDescription
5 |
6 | let package = Package(
7 | name: "SwiftRichString",
8 | products: [
9 | // Products define the executables and libraries produced by a package, and make them visible to other packages.
10 | .library(
11 | name: "SwiftRichString",
12 | targets: ["SwiftRichString"]),
13 | ],
14 | dependencies: [
15 | // Dependencies declare other packages that this package depends on.
16 | // .package(url: /* package url */, from: "1.0.0"),
17 | ],
18 | targets: [
19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
20 | // Targets can depend on other targets in this package, and on products in packages which this package depends on.
21 | .target(
22 | name: "SwiftRichString",
23 | dependencies: []),
24 | .testTarget(
25 | name: "SwiftRichStringTests",
26 | dependencies: ["SwiftRichString"]),
27 | ]
28 | )
29 |
--------------------------------------------------------------------------------
/Sources/SwiftRichString/Attributes/ColorConvertible.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftRichString
3 | // Elegant Strings & Attributed Strings Toolkit for Swift
4 | //
5 | // Created by Daniele Margutti.
6 | // Copyright © 2018 Daniele Margutti. All rights reserved.
7 | //
8 | // Web: http://www.danielemargutti.com
9 | // Email: hello@danielemargutti.com
10 | // Twitter: @danielemargutti
11 | //
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in
21 | // all copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29 | // THE SOFTWARE.
30 |
31 | import Foundation
32 | #if os(OSX)
33 | import AppKit
34 | #else
35 | import UIKit
36 | #endif
37 |
38 | // MARK: - ColorConvertible
39 |
40 | /// `ColorConvertible` protocol conformance is used to pass your own instance of a color representable object
41 | /// as color's attributes for several properties inside a style. Style get the color instance from your object
42 | /// and use it inside for string attributes.
43 | /// Both `String` and `UIColor`/`NSColor` already conforms this protocol.
44 | public protocol ColorConvertible {
45 | /// Transform a instance of a `ColorConvertible` conform object to a valid `UIColor`/`NSColor`.
46 | var color: Color { get }
47 | }
48 |
49 | // MARK: - ColorConvertible for `UIColor`/`NSColor`
50 |
51 | extension Color: ColorConvertible {
52 |
53 | /// Just return itself
54 | public var color: Color {
55 | return self
56 | }
57 |
58 | /// Initialize a new color from HEX string representation.
59 | ///
60 | /// - Parameter hexString: hex string
61 | public convenience init(hexString: String) {
62 | let hexString = hexString.trimmingCharacters(in: .whitespacesAndNewlines)
63 | let scanner = Scanner(string: hexString)
64 |
65 | if hexString.hasPrefix("#") {
66 | scanner.scanLocation = 1
67 | }
68 |
69 | var color: UInt32 = 0
70 |
71 | if scanner.scanHexInt32(&color) {
72 | self.init(hex: color, useAlpha: hexString.count > 7)
73 | } else {
74 | self.init(hex: 0x000000)
75 | }
76 | }
77 |
78 | /// Initialize a new color from HEX string as UInt32 with optional alpha chanell.
79 | ///
80 | /// - Parameters:
81 | /// - hex: hex value
82 | /// - alphaChannel: `true` to include alpha channel, `false` to make it opaque.
83 | public convenience init(hex: UInt32, useAlpha alphaChannel: Bool = false) {
84 | let mask = UInt32(0xFF)
85 |
86 | let r = hex >> (alphaChannel ? 24 : 16) & mask
87 | let g = hex >> (alphaChannel ? 16 : 8) & mask
88 | let b = hex >> (alphaChannel ? 8 : 0) & mask
89 | let a = alphaChannel ? hex & mask : 255
90 |
91 | let red = CGFloat(r) / 255
92 | let green = CGFloat(g) / 255
93 | let blue = CGFloat(b) / 255
94 | let alpha = CGFloat(a) / 255
95 |
96 | self.init(red: red, green: green, blue: blue, alpha: alpha)
97 | }
98 |
99 | }
100 |
101 | // MARK: - ColorConvertible for `String`
102 |
103 | extension String: ColorConvertible {
104 |
105 | /// Transform a string to a color. String must be a valid HEX representation of the color.
106 | public var color: Color {
107 | return Color(hexString: self)
108 | }
109 |
110 | }
111 |
112 |
--------------------------------------------------------------------------------
/Sources/SwiftRichString/Attributes/CommonsAttributes.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftRichString
3 | // Elegant Strings & Attributed Strings Toolkit for Swift
4 | //
5 | // Created by Daniele Margutti.
6 | // Copyright © 2018 Daniele Margutti. All rights reserved.
7 | //
8 | // Web: http://www.danielemargutti.com
9 | // Email: hello@danielemargutti.com
10 | // Twitter: @danielemargutti
11 | //
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in
21 | // all copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29 | // THE SOFTWARE.
30 |
31 | import Foundation
32 | import CoreGraphics
33 |
34 | //MARK: - Typealiases
35 |
36 | #if os(OSX)
37 | import AppKit
38 |
39 | public typealias Color = NSColor
40 | public typealias Image = NSImage
41 | public typealias Font = NSFont
42 | public typealias FontDescriptor = NSFontDescriptor
43 | public typealias SymbolicTraits = NSFontDescriptor.SymbolicTraits
44 | public typealias LineBreak = NSLineBreakMode
45 |
46 | let FontDescriptorFeatureSettingsAttribute = NSFontDescriptor.AttributeName.featureSettings
47 | let FontFeatureTypeIdentifierKey = NSFontDescriptor.FeatureKey.typeIdentifier
48 | let FontFeatureSelectorIdentifierKey = NSFontDescriptor.FeatureKey.selectorIdentifier
49 |
50 | #else
51 | import UIKit
52 |
53 | public typealias Color = UIColor
54 | public typealias Image = UIImage
55 | public typealias Font = UIFont
56 | public typealias FontDescriptor = UIFontDescriptor
57 | public typealias SymbolicTraits = UIFontDescriptor.SymbolicTraits
58 | public typealias LineBreak = NSLineBreakMode
59 |
60 | let FontDescriptorFeatureSettingsAttribute = UIFontDescriptor.AttributeName.featureSettings
61 | let FontFeatureTypeIdentifierKey = UIFontDescriptor.FeatureKey.featureIdentifier
62 | let FontFeatureSelectorIdentifierKey = UIFontDescriptor.FeatureKey.typeIdentifier
63 |
64 | #endif
65 |
66 | //MARK: - Kerning
67 |
68 | /// An enumeration representing the tracking to be applied.
69 | ///
70 | /// - point: point value.
71 | /// - adobe: adobe format point value.
72 | public enum Kerning {
73 | case point(CGFloat)
74 | case adobe(CGFloat)
75 |
76 | public func kerning(for font: Font?) -> CGFloat {
77 | switch self {
78 | case .point(let kernValue):
79 | return kernValue
80 | case .adobe(let adobeTracking):
81 | let AdobeTrackingDivisor: CGFloat = 1000.0
82 | if font == nil {
83 | print("Missing font for apply tracking; 0 is the fallback.")
84 | }
85 | return (font?.pointSize ?? 0) * (adobeTracking / AdobeTrackingDivisor)
86 | }
87 | }
88 |
89 | }
90 |
91 | //MARK: - Ligatures
92 |
93 | /// Ligatures cause specific character combinations to be rendered using a single custom glyph that corresponds
94 | /// to those characters.
95 | ///
96 | /// - disabled: use only required ligatures when setting text, for the glyphs in the selection
97 | /// if the receiver is a rich text view, or for all glyphs if it’s a plain text view.
98 | /// - defaults: use the standard ligatures available for the fonts and languages used when setting text,
99 | /// for the glyphs in the selection if the receiver is a rich text view, or for all glyphs if it’s a plain text view.
100 | /// - all: use all ligatures available for the fonts and languages used when setting text, for the glyphs
101 | /// in the selection if the receiver is a rich text view, or for all glyphs if it’s a
102 | /// plain text view (not supported on iOS).
103 | public enum Ligatures: Int {
104 | case disabled = 0
105 | case defaults = 1
106 | #if os(OSX)
107 | case all = 2
108 | #endif
109 | }
110 |
111 | //MARK: - HeadingLevel
112 |
113 | /// Specify the heading level of the text.
114 | /// Value is a number in the range 0 to 6.
115 | /// Use 0 to indicate the absence of a specific heading level and use other numbers to indicate the heading level.
116 | ///
117 | /// - none: no heading
118 | /// - one: level 1
119 | /// - two: level 2
120 | /// - three: level 3
121 | /// - four: level 4
122 | /// - five: level 5
123 | /// - six: level 6
124 | public enum HeadingLevel: Int {
125 | case none = 0
126 | case one = 1
127 | case two = 2
128 | case three = 3
129 | case four = 4
130 | case five = 5
131 | case six = 6
132 | }
133 |
134 | //MARK: - Trait Variants
135 |
136 | /// Describe a trait variant for font
137 | public struct TraitVariant: OptionSet {
138 | public var rawValue: Int
139 |
140 | /// The font typestyle is italic
141 | public static let italic = TraitVariant(rawValue: 1 << 0)
142 |
143 | /// The font typestyle is boldface
144 | public static let bold = TraitVariant(rawValue: 1 << 1)
145 |
146 | // The font typestyle is expanded. Expanded and condensed traits are mutually exclusive.
147 | public static let expanded = TraitVariant(rawValue: 1 << 2)
148 |
149 | /// The font typestyle is condensed. Expanded and condensed traits are mutually exclusive
150 | public static let condensed = TraitVariant(rawValue: 1 << 3)
151 |
152 | /// The font uses vertical glyph variants and metrics.
153 | public static let vertical = TraitVariant(rawValue: 1 << 4)
154 |
155 | /// The font synthesizes appropriate attributes for user interface rendering, such as control titles, if necessary.
156 | public static let uiOptimized = TraitVariant(rawValue: 1 << 5)
157 |
158 | /// The font use a tigher line spacing variant.
159 | public static let tightLineSpacing = TraitVariant(rawValue: 1 << 6)
160 |
161 | /// The font use a loose line spacing variant.
162 | public static let looseLineSpacing = TraitVariant(rawValue: 1 << 7)
163 |
164 | public init(rawValue: Int) {
165 | self.rawValue = rawValue
166 | }
167 | }
168 |
169 |
170 | extension TraitVariant {
171 |
172 | var symbolicTraits: SymbolicTraits {
173 | var traits: SymbolicTraits = []
174 | if contains(.italic) {
175 | traits.insert(SymbolicTraits.italic)
176 | }
177 | if contains(.bold) {
178 | traits.insert(.bold)
179 | }
180 | if contains(.expanded) {
181 | traits.insert(.expanded)
182 | }
183 | if contains(.condensed) {
184 | traits.insert(.condensed)
185 | }
186 | if contains(.vertical) {
187 | traits.insert(.vertical)
188 | }
189 | if contains(.uiOptimized) {
190 | traits.insert(.uiOptimized)
191 | }
192 | if contains(.tightLineSpacing) {
193 | traits.insert(.tightLineSpacing)
194 | }
195 | if contains(.looseLineSpacing) {
196 | traits.insert(.looseLineSpacing)
197 | }
198 |
199 | return traits
200 | }
201 |
202 | }
203 |
204 | //MARK: - Symbolic Traits (UIFontDescriptorSymbolicTraits) Extensions
205 |
206 | extension SymbolicTraits {
207 | #if os(iOS) || os(tvOS) || os(watchOS)
208 | static var italic: SymbolicTraits {
209 | return .traitItalic
210 | }
211 | static var bold: SymbolicTraits {
212 | return .traitBold
213 | }
214 | static var expanded: SymbolicTraits {
215 | return .traitExpanded
216 | }
217 | static var condensed: SymbolicTraits {
218 | return .traitCondensed
219 | }
220 | static var vertical: SymbolicTraits {
221 | return .traitVertical
222 | }
223 | static var uiOptimized: SymbolicTraits {
224 | return .traitUIOptimized
225 | }
226 | static var tightLineSpacing: SymbolicTraits {
227 | return .traitTightLeading
228 | }
229 | static var looseLineSpacing: SymbolicTraits {
230 | return .traitLooseLeading
231 | }
232 | #else
233 | static var uiOptimized: SymbolicTraits {
234 | return .UIOptimized
235 | }
236 | static var tightLineSpacing: SymbolicTraits {
237 | return .tightLeading
238 | }
239 | static var looseLineSpacing: SymbolicTraits {
240 | return .looseLeading
241 | }
242 | #endif
243 | }
244 |
--------------------------------------------------------------------------------
/Sources/SwiftRichString/Attributes/DynamicText.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftRichString
3 | // Elegant Strings & Attributed Strings Toolkit for Swift
4 | //
5 | // Created by Daniele Margutti.
6 | // Copyright © 2018 Daniele Margutti. All rights reserved.
7 | //
8 | // Web: http://www.danielemargutti.com
9 | // Email: hello@danielemargutti.com
10 | // Twitter: @danielemargutti
11 | //
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in
21 | // all copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29 | // THE SOFTWARE.
30 |
31 | #if os(tvOS) || os(watchOS) || os(iOS)
32 | import UIKit
33 |
34 | /// DynamicText encapsulate the attributes for fonts to automatically scale to match the current Dynamic Type settings. It uses UIFontMetrics.
35 | public class DynamicText {
36 |
37 | /// Set the dynamic size text style.
38 | /// You can pass any `UIFont.TextStyle` value, if nil UIFontMetrics.default will be used,
39 | /// which uses the body text style.
40 | public var style: UIFont.TextStyle?
41 |
42 | #if os(OSX) || os(iOS) || os(tvOS)
43 | /// The trait collection to use when determining compatibility. The returned
44 | /// font is appropriate for use in an interface that adopts the specified traits.
45 | public var traitCollection: UITraitCollection?
46 | #endif
47 |
48 | /// Set the maximum size
49 | /// allowed for the font/text. Use this value to constrain the font to
50 | /// the specified size when your interface cannot accommodate text that is any larger.
51 | public var maximumSize: CGFloat?
52 |
53 | public typealias InitHandler = ((DynamicText) -> (Void))
54 |
55 | //MARK: - INIT
56 |
57 | /// Initialize a new dynamic text with optional configuration handler callback.
58 | ///
59 | /// - Parameter handler: configuration handler callback.
60 | public init(_ handler: InitHandler? = nil) {
61 | handler?(self)
62 | }
63 | }
64 |
65 | #endif
66 |
--------------------------------------------------------------------------------
/Sources/SwiftRichString/Attributes/FontConvertible.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftRichString
3 | // Elegant Strings & Attributed Strings Toolkit for Swift
4 | //
5 | // Created by Daniele Margutti.
6 | // Copyright © 2018 Daniele Margutti. All rights reserved.
7 | //
8 | // Web: http://www.danielemargutti.com
9 | // Email: hello@danielemargutti.com
10 | // Twitter: @danielemargutti
11 | //
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in
21 | // all copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29 | // THE SOFTWARE.
30 |
31 | import Foundation
32 |
33 | #if os(OSX)
34 | import AppKit
35 | #else
36 | import UIKit
37 | #endif
38 |
39 | //MARK: - FontConvertible Protocol
40 |
41 | /// FontConvertible Protocol; you can implement conformance to any object.
42 | /// By defailt both `String` and `UIFont`/`NSFont` are conform to this protocol.
43 | public protocol FontConvertible {
44 | /// Transform the instance of the object to a valid `UIFont`/`NSFont` instance.
45 | ///
46 | /// - Parameter size: optional size of the font.
47 | /// - Returns: valid font instance.
48 | func font(size: CGFloat?) -> Font
49 | }
50 |
51 |
52 | // MARK: - FontConvertible for UIFont/NSFont
53 | extension Font: FontConvertible {
54 |
55 | /// Return the same instance of the font with specified size.
56 | ///
57 | /// - Parameter size: size of the font in points. If size is `nil`, `Font.systemFontSize` is used.
58 | /// - Returns: instance of the font.
59 | public func font(size: CGFloat?) -> Font {
60 | #if os(tvOS)
61 | return Font(name: self.fontName, size: (size ?? TVOS_SYSTEMFONT_SIZE))!
62 | #elseif os(watchOS)
63 | return Font(name: self.fontName, size: (size ?? WATCHOS_SYSTEMFONT_SIZE))!
64 | #elseif os(macOS)
65 | return Font(descriptor: self.fontDescriptor, size: (size ?? Font.systemFontSize))!
66 | #else
67 | return Font(descriptor: self.fontDescriptor, size: (size ?? Font.systemFontSize))
68 | #endif
69 | }
70 |
71 | }
72 |
73 | // MARK: - FontConvertible for String
74 | extension String: FontConvertible {
75 |
76 | /// Transform a string to a valid `UIFont`/`NSFont` instance.
77 | /// String must contain a valid Postscript font's name.
78 | ///
79 | /// - Parameter size: size of the font.
80 | /// - Returns: instance of the font.
81 | public func font(size: CGFloat?) -> Font {
82 | #if os(tvOS)
83 | return Font(name: self, size: (size ?? TVOS_SYSTEMFONT_SIZE))!
84 | #elseif os(watchOS)
85 | return Font(name: self, size: (size ?? WATCHOS_SYSTEMFONT_SIZE))!
86 | #else
87 | return Font(name: self, size: (size ?? Font.systemFontSize))!
88 | #endif
89 | }
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/Sources/SwiftRichString/Attributes/TextTransform.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftRichString
3 | // Elegant Strings & Attributed Strings Toolkit for Swift
4 | //
5 | // Created by Daniele Margutti.
6 | // Copyright © 2018 Daniele Margutti. All rights reserved.
7 | //
8 | // Web: http://www.danielemargutti.com
9 | // Email: hello@danielemargutti.com
10 | // Twitter: @danielemargutti
11 | //
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in
21 | // all copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29 | // THE SOFTWARE.
30 |
31 | import Foundation
32 |
33 | public enum TextTransform {
34 | public typealias TransformFunction = (String) -> String
35 |
36 | case lowercase
37 | case uppercase
38 | case capitalized
39 |
40 | case lowercaseWithLocale(Locale)
41 | case uppercaseWithLocale(Locale)
42 | case capitalizedWithLocale(Locale)
43 | case custom(TransformFunction)
44 |
45 | var transformer: TransformFunction {
46 | switch self {
47 | case .lowercase:
48 | return { string in
49 | if #available(iOS 9.0, iOSApplicationExtension 9.0, *) {
50 | return string.localizedLowercase
51 | } else {
52 | return string.lowercased(with: Locale.current)
53 | }
54 | }
55 |
56 | case .uppercase:
57 | return { string in
58 | if #available(iOS 9.0, iOSApplicationExtension 9.0, *) {
59 | return string.localizedUppercase
60 | } else {
61 | return string.uppercased(with: Locale.current)
62 | }
63 | }
64 |
65 | case .capitalized:
66 | return { string in
67 | if #available(iOS 9.0, iOSApplicationExtension 9.0, *) {
68 | return string.localizedCapitalized
69 | } else {
70 | return string.capitalized(with: Locale.current)
71 | }
72 | }
73 |
74 | case .lowercaseWithLocale(let locale):
75 | return { string in
76 | string.lowercased(with: locale)
77 | }
78 |
79 | case .uppercaseWithLocale(let locale):
80 | return { string in
81 | string.uppercased(with: locale)
82 | }
83 |
84 | case .capitalizedWithLocale(let locale):
85 | return { string in
86 | string.capitalized(with: locale)
87 | }
88 |
89 | case .custom(let transform):
90 | return transform
91 | }
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/Sources/SwiftRichString/Attributes/URLRepresentable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftRichString
3 | // Elegant Strings & Attributed Strings Toolkit for Swift
4 | //
5 | // Created by Daniele Margutti.
6 | // Copyright © 2018 Daniele Margutti. All rights reserved.
7 | //
8 | // Web: http://www.danielemargutti.com
9 | // Email: hello@danielemargutti.com
10 | // Twitter: @danielemargutti
11 | //
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in
21 | // all copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29 | // THE SOFTWARE.
30 |
31 | import Foundation
32 |
33 | public protocol URLRepresentable {
34 | var url: URL { get }
35 | }
36 |
37 | extension URL: URLRepresentable {
38 |
39 | public var url: URL {
40 | return self
41 | }
42 |
43 | public init?(string: String?) {
44 | guard let string = string else {
45 | return nil
46 | }
47 | self.init(string: string)
48 | }
49 |
50 | }
51 |
52 |
--------------------------------------------------------------------------------
/Sources/SwiftRichString/Extensions/AttributedString+Attachments.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftRichString
3 | // Elegant Strings & Attributed Strings Toolkit for Swift
4 | //
5 | // Created by Daniele Margutti.
6 | // Copyright © 2018 Daniele Margutti. All rights reserved.
7 | //
8 | // Web: http://www.danielemargutti.com
9 | // Email: hello@danielemargutti.com
10 | // Twitter: @danielemargutti
11 | //
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in
21 | // all copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29 | // THE SOFTWARE.
30 |
31 | import Foundation
32 |
33 | #if os(OSX)
34 | import AppKit
35 | #else
36 | import UIKit
37 | #endif
38 |
39 | public extension AttributedString {
40 |
41 | #if os(iOS)
42 |
43 | /// Initialize a new text attachment with a remote image resource.
44 | /// Image will be loaded asynchronously after the text appear inside the control.
45 | ///
46 | /// - Parameters:
47 | /// - imageURL: url of the image. If url is not valid resource will be not downloaded.
48 | /// - bounds: set a non `nil` value to express set the rect of attachment.
49 | convenience init?(imageURL: String?, bounds: String? = nil) {
50 | guard let imageURL = imageURL, let url = URL(string: imageURL) else {
51 | return nil
52 | }
53 |
54 | let attachment = AsyncTextAttachment()
55 | attachment.imageURL = url
56 |
57 | if let bounds = CGRect(string: bounds) {
58 | attachment.bounds = bounds
59 | }
60 |
61 | self.init(attachment: attachment)
62 | }
63 |
64 | #endif
65 |
66 | #if os(iOS) || os(OSX)
67 |
68 | /// Initialize a new text attachment with local image contained into the assets.
69 | ///
70 | /// - Parameters:
71 | /// - imageNamed: name of the image into the assets; if `nil` resource will be not loaded.
72 | /// - bounds: set a non `nil` value to express set the rect of attachment.
73 | convenience init?(imageNamed: String?, bounds: String? = nil) {
74 | guard let imageNamed = imageNamed else {
75 | return nil
76 | }
77 |
78 | let image = Image(named: imageNamed)
79 | self.init(image: image, bounds: bounds)
80 | }
81 |
82 | /// Initialize a new attributed string from an image.
83 | ///
84 | /// - Parameters:
85 | /// - image: image to use.
86 | /// - bounds: location and size of the image, if `nil` the default bounds is applied.
87 | convenience init?(image: Image?, bounds: String? = nil) {
88 | guard let image = image else {
89 | return nil
90 | }
91 |
92 | #if os(OSX)
93 | let attachment = NSTextAttachment(data: image.pngData()!, ofType: "png")
94 | #else
95 | var attachment: NSTextAttachment!
96 | if #available(iOS 13.0, *) {
97 | // Due to a bug (?) in UIKit we should use two methods to allocate the text attachment
98 | // in order to render the image as template or original. If we use the
99 | // NSTextAttachment(image: image) with a .alwaysOriginal rendering mode it will be
100 | // ignored.
101 | if image.renderingMode == .alwaysTemplate {
102 | attachment = NSTextAttachment(image: image)
103 | } else {
104 | attachment = NSTextAttachment()
105 | attachment.image = image.withRenderingMode(.alwaysOriginal)
106 | }
107 | } else {
108 | // It does not work on iOS12, return empty set.s
109 | // attachment = NSTextAttachment(data: image.pngData()!, ofType: "png")
110 | attachment = NSTextAttachment()
111 | attachment.image = image.withRenderingMode(.alwaysOriginal)
112 | }
113 | #endif
114 |
115 | if let boundsRect = CGRect(string: bounds) {
116 | attachment.bounds = boundsRect
117 | }
118 |
119 | self.init(attachment: attachment)
120 | }
121 |
122 | #endif
123 |
124 | }
125 |
--------------------------------------------------------------------------------
/Sources/SwiftRichString/Extensions/AttributedString+Extension.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftRichString
3 | // Elegant Strings & Attributed Strings Toolkit for Swift
4 | //
5 | // Created by Daniele Margutti.
6 | // Copyright © 2018 Daniele Margutti. All rights reserved.
7 | //
8 | // Web: http://www.danielemargutti.com
9 | // Email: hello@danielemargutti.com
10 | // Twitter: @danielemargutti
11 | //
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in
21 | // all copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29 | // THE SOFTWARE.
30 |
31 | import Foundation
32 | #if os(OSX)
33 | import AppKit
34 | #else
35 | import UIKit
36 | #endif
37 |
38 | //MARK: - AttributedString Extension
39 |
40 | // The following methods are used to alter an existing attributed string with attributes specified by styles.
41 | public extension AttributedString {
42 |
43 | /// Append existing style's attributed, registered in `StyleManager`, to the receiver string (or substring).
44 | ///
45 | /// - Parameters:
46 | /// - style: valid style name registered in `StyleManager`.
47 | /// If invalid, the same instance of the receiver is returned unaltered.
48 | /// - range: range of substring where style is applied, `nil` to use the entire string.
49 | /// - Returns: same instance of the receiver with - eventually - modified attributes.
50 | @discardableResult
51 | func add(style: String, range: NSRange? = nil) -> AttributedString {
52 | guard let style = Styles[style] else { return self }
53 | return style.add(to: self, range: range)
54 | }
55 |
56 | /// Append ordered sequence of styles registered in `StyleManager` to the receiver.
57 | /// Styles are merged in order to produce a single attribute dictionary which is therefore applied to the string.
58 | ///
59 | /// - Parameters:
60 | /// - styles: ordered list of styles to apply.
61 | /// - range: range of substring where style is applied, `nil` to use the entire string.
62 | /// - Returns: same instance of the receiver with - eventually - modified attributes.
63 | @discardableResult
64 | func add(styles: [String], range: NSRange? = nil) -> AttributedString {
65 | guard let styles = Styles[styles] else { return self }
66 | return styles.mergeStyle().set(to: self, range: range)
67 | }
68 |
69 | /// Replace any existing attributed string's style into the receiver/substring of the receiver
70 | /// with the style which has the specified name and is registered into `StyleManager`.
71 | ///
72 | /// - Parameters:
73 | /// - style: style name to apply. Style instance must be registered in `StyleManager` to be applied.
74 | /// - range: range of substring where style is applied, `nil` to use the entire string.
75 | /// - Returns: same instance of the receiver with - eventually - modified attributes.
76 | @discardableResult
77 | func set(style: String, range: NSRange? = nil) -> AttributedString {
78 | guard let style = Styles[style] else { return self }
79 | return style.set(to: self, range: range)
80 | }
81 |
82 | /// Replace any existing attributed string's style into the receiver/substring of the receiver
83 | /// with a style which is an ordered merge of styles passed.
84 | /// Styles are passed as name and you must register them into `StyleManager` before using this function.
85 | ///
86 | /// - Parameters:
87 | /// - styles: styles name to apply. Instances must be registered into `StyleManager`.
88 | /// - range: range of substring where style is applied, `nil` to use the entire string.
89 | /// - Returns: same instance of the receiver with - eventually - modified attributes.
90 | @discardableResult
91 | func set(styles: [String], range: NSRange? = nil) -> AttributedString {
92 | guard let styles = Styles[styles] else { return self }
93 | return styles.mergeStyle().set(to: self, range: range)
94 | }
95 |
96 | /// Append passed style to the receiver.
97 | ///
98 | /// - Parameters:
99 | /// - style: style to apply.
100 | /// - range: range of substring where style is applied, `nil` to use the entire string.
101 | /// - Returns: same instance of the receiver with - eventually - modified attributes.
102 | @discardableResult
103 | func add(style: StyleProtocol, range: NSRange? = nil) -> AttributedString {
104 | return style.add(to: self, range: range)
105 | }
106 |
107 | /// Append passed sequences of styles to the receiver.
108 | /// Sequences are merged in order in a single style's attribute which is therefore applied to the string.
109 | ///
110 | /// - Parameters:
111 | /// - styles: styles to apply, in order.
112 | /// - range: range of substring where style is applied, `nil` to use the entire string.
113 | /// - Returns: same instance of the receiver with - eventually - modified attributes.
114 | @discardableResult
115 | func add(styles: [StyleProtocol], range: NSRange? = nil) -> AttributedString {
116 | return styles.mergeStyle().add(to: self, range: range)
117 | }
118 |
119 | /// Replace the attributes of the string with passed style.
120 | ///
121 | /// - Parameters:
122 | /// - style: style to apply.
123 | /// - range: range of substring where style is applied, `nil` to use the entire string.
124 | /// - Returns: same instance of the receiver with - eventually - modified attributes.
125 | @discardableResult
126 | func set(style: StyleProtocol, range: NSRange? = nil) -> AttributedString {
127 | return style.set(to: self, range: range)
128 | }
129 |
130 | /// Replace the attributes of the string with a style which is an ordered merge of passed
131 | /// styles sequence.
132 | ///
133 | /// - Parameters:
134 | /// - styles: ordered list of styles to apply.
135 | /// - range: range of substring where style is applied, `nil` to use the entire string.
136 | /// - Returns: same instance of the receiver with - eventually - modified attributes.
137 | @discardableResult
138 | func set(styles: [StyleProtocol], range: NSRange? = nil) -> AttributedString {
139 | return styles.mergeStyle().set(to: self, range: range)
140 | }
141 |
142 | /// Remove passed attribute's keys from the receiver.
143 | ///
144 | /// - Parameters:
145 | /// - keys: attribute's keys to remove.
146 | /// - range: range of substring where style will be removed, `nil` to use the entire string.
147 | /// - Returns: same instance of the receiver with - eventually - modified attributes.
148 | @discardableResult
149 | func removeAttributes(_ keys: [NSAttributedString.Key], range: NSRange) -> Self {
150 | keys.forEach { self.removeAttribute($0, range: range) }
151 | return self
152 | }
153 |
154 | /// Remove all keys defined into passed style from the receiver.
155 | ///
156 | /// - Parameter style: style to use.
157 | /// - Returns: same instance of the receiver with - eventually - modified attributes.
158 | func remove(_ style: StyleProtocol) -> Self {
159 | self.removeAttributes(Array(style.attributes.keys), range: NSMakeRange(0, self.length))
160 | return self
161 | }
162 |
163 | }
164 |
165 | //MARK: - Operations
166 |
167 | /// Merge two attributed string in a single new attributed string.
168 | ///
169 | /// - Parameters:
170 | /// - lhs: attributed string.
171 | /// - rhs: attributed string.
172 | /// - Returns: new attributed string concatenation of two strings.
173 | public func + (lhs: AttributedString, rhs: AttributedString) -> AttributedString {
174 | let final = NSMutableAttributedString(attributedString: lhs)
175 | final.append(rhs)
176 | return final
177 | }
178 |
179 | /// Merge a plain string with an attributed string to produce a new attributed string.
180 | ///
181 | /// - Parameters:
182 | /// - lhs: plain string.
183 | /// - rhs: attributed string.
184 | /// - Returns: new attributed string.
185 | public func + (lhs: String, rhs: AttributedString) -> AttributedString {
186 | let final = NSMutableAttributedString(string: lhs)
187 | final.append(rhs)
188 | return final
189 | }
190 |
191 | /// Merge an attributed string with a plain string to produce a new attributed string.
192 | ///
193 | /// - Parameters:
194 | /// - lhs: attributed string.
195 | /// - rhs: plain string.
196 | /// - Returns: new attributed string.
197 | public func + (lhs: AttributedString, rhs: String) -> AttributedString {
198 | let final = NSMutableAttributedString(attributedString: lhs)
199 | final.append(NSMutableAttributedString(string: rhs))
200 | return final
201 | }
202 |
--------------------------------------------------------------------------------
/Sources/SwiftRichString/Extensions/AttributedString+FunctionBuilder.swift:
--------------------------------------------------------------------------------
1 | ////
2 | // SwiftRichString
3 | // Elegant Strings & Attributed Strings Toolkit for Swift
4 | //
5 | // Created by Daniele Margutti.
6 | // Copyright © 2018 Daniele Margutti. All rights reserved.
7 | //
8 | // Web: http://www.danielemargutti.com
9 | // Email: hello@danielemargutti.com
10 | // Twitter: @danielemargutti
11 | //
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in
21 | // all copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29 | // THE SOFTWARE.
30 |
31 | import Foundation
32 |
33 | @_functionBuilder
34 | public class AttributedStringBuilder {
35 | public static func buildBlock(_ components: AttributedString...) -> AttributedString {
36 | let result = NSMutableAttributedString(string: "")
37 |
38 | return components.reduce(into: result) { (result, current) in result.append(current) }
39 | }
40 | }
41 |
42 | extension AttributedString {
43 |
44 | public class func composing(@AttributedStringBuilder _ parts: () -> AttributedString) -> AttributedString {
45 | return parts()
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/Sources/SwiftRichString/Extensions/String+Ext.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftRichString
3 | // Elegant Strings & Attributed Strings Toolkit for Swift
4 | //
5 | // Created by Daniele Margutti.
6 | // Copyright © 2018 Daniele Margutti. All rights reserved.
7 | //
8 | // Web: http://www.danielemargutti.com
9 | // Email: hello@danielemargutti.com
10 | // Twitter: @danielemargutti
11 | //
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in
21 | // all copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29 | // THE SOFTWARE.
30 |
31 | import Foundation
32 | #if os(OSX)
33 | import AppKit
34 | #else
35 | import UIKit
36 | #endif
37 |
38 | //MARK: - String Extension
39 |
40 | // The following methods are used to produce an attributed string by a plain string.
41 | public extension String {
42 |
43 | //MARK: RENDERING FUNCTIONS
44 |
45 | /// Apply style with given name defines in global `StylesManager` to the receiver string.
46 | /// If required style is not available this function return `nil`.
47 | ///
48 | /// - Parameters:
49 | /// - style: name of style registered in `StylesManager` singleton.
50 | /// - range: range of substring where style is applied, `nil` to use the entire string.
51 | /// - Returns: rendered attributed string, `nil` if style is not registered.
52 | func set(style: String, range: NSRange? = nil) -> AttributedString? {
53 | return StylesManager.shared[style]?.set(to: self, range: range)
54 | }
55 |
56 | /// Apply a sequence of styles defied in global `StylesManager` to the receiver string.
57 | /// Unregistered styles are ignored.
58 | /// Sequence produces a single merge style where the each n+1 element of the sequence may
59 | /// override existing key set by previous applied style.
60 | /// Resulting attributes dictionary is threfore applied to the string.
61 | ///
62 | /// - Parameters:
63 | /// - styles: ordered list of styles name to apply. Styles must be registed in `StylesManager`.
64 | /// - range: range of substring where style is applied, `nil` to use the entire string.
65 | /// - Returns: attributed string, `nil` if all specified styles required are not registered.
66 | func set(styles: [String], range: NSRange? = nil) -> AttributedString? {
67 | return StylesManager.shared[styles]?.mergeStyle().set(to: self, range: range)
68 | }
69 |
70 | /// Apply passed style to the receiver string.
71 | ///
72 | /// - Parameters:
73 | /// - style: style to apply.
74 | /// - range: range of substring where style is applied, `nil` to use the entire string.
75 | /// - Returns: rendered attributed string.
76 | func set(style: StyleProtocol, range: NSRange? = nil) -> AttributedString {
77 | return style.set(to: self, range: range)
78 | }
79 |
80 | /// Apply passed sequence of `StyleProtocol` instances to the receiver.
81 | /// Sequence produces a single merge style where the each n+1 element of the sequence may
82 | /// override existing key set by previous applied style.
83 | /// Resulting attributes dictionary is threfore applied to the string.
84 | ///
85 | /// - Parameters:
86 | /// - styles: ordered list of styles to apply. Styles must be registed in `StylesManager`.
87 | /// - range: range of substring where style is applied, `nil` to use the entire string.
88 | /// - Returns: attributed string.
89 | func set(styles: [StyleProtocol], range: NSRange? = nil) -> AttributedString {
90 | return styles.mergeStyle().set(to: self, range: range)
91 | }
92 |
93 | }
94 |
95 | //MARK: - Operations
96 |
97 | /// Create a new attributed string using + where the left operand is a plain string and left is a style.
98 | ///
99 | /// - Parameters:
100 | /// - lhs: plain string.
101 | /// - rhs: style to apply.
102 | /// - Returns: rendered attributed string instance
103 | public func + (lhs: String, rhs: StyleProtocol) -> AttributedString {
104 | return rhs.set(to: lhs, range: nil)
105 | }
106 |
107 |
--------------------------------------------------------------------------------
/Sources/SwiftRichString/Extensions/String+Subscript.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftRichString
3 | // Elegant Strings & Attributed Strings Toolkit for Swift
4 | //
5 | // Created by Daniele Margutti.
6 | // Copyright © 2018 Daniele Margutti. All rights reserved.
7 | //
8 | // Web: http://www.danielemargutti.com
9 | // Email: hello@danielemargutti.com
10 | // Twitter: @danielemargutti
11 | //
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in
21 | // all copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29 | // THE SOFTWARE.
30 |
31 | import Foundation
32 |
33 | public extension NSRange {
34 |
35 | /// Convert receiver in String's `Range` for given string instance.
36 | ///
37 | /// - Parameter str: source string
38 | /// - Returns: range, `nil` if invalid
39 | func `in`(_ str: String) -> Range? {
40 | return Range(self, in: str)
41 | }
42 |
43 | }
44 |
45 | // MARK: - Subrange Subscript
46 | public extension String {
47 |
48 | /// Convert an NSRange to String range.
49 | ///
50 | /// - Parameter nsRange: origin NSRange instance.
51 | /// - Returns: Range
52 | func rangeFrom(nsRange : NSRange) -> Range? {
53 | return Range(nsRange, in: self)
54 | }
55 |
56 | /// Return substring from receiver starting at given index for a given length.
57 | /// Return `nil` for invalid ranges.
58 | ///
59 | ///
60 | /// - Parameters:
61 | /// - from: starting index.
62 | /// - length: length of string,
63 | /// - Returns: substring
64 | func substring(from: Int?, length: Int) -> String? {
65 | guard length > 0 else { return nil }
66 | let start = from ?? 0
67 | let end = min(count, max(0, start) + length)
68 | guard start < end else { return nil }
69 | return self[start.. Character? {
78 | guard !self.isEmpty, let stringIndex = self.index(startIndex, offsetBy: index, limitedBy: self.index(before: endIndex)) else { return nil }
79 | return self[stringIndex]
80 | }
81 |
82 |
83 | /// Allows to get a substring at specified range.
84 | /// `String[0..<1]`
85 | ///
86 | /// - Parameter value: substring
87 | subscript(range: Range) -> Substring? {
88 | guard let left = offset(by: range.lowerBound) else { return nil }
89 | guard let right = index(left, offsetBy: range.upperBound - range.lowerBound,
90 | limitedBy: endIndex) else { return nil }
91 | return self[left..) -> Substring? {
99 | if value.upperBound < 0 {
100 | guard abs(value.upperBound) <= count else { return nil }
101 | return self[..<(count - abs(value.upperBound))]
102 | }
103 | guard let right = offset(by: value.upperBound) else { return nil }
104 | return self[..) -> Substring? {
112 | if range.upperBound < 0 {
113 | guard abs(range.lowerBound) <= count else { return nil }
114 | return self[(count - abs(range.lowerBound))...]
115 | }
116 | guard let left = offset(by: range.lowerBound) else { return nil }
117 | guard let right = index(left, offsetBy: range.upperBound - range.lowerBound, limitedBy: endIndex) else { return nil }
118 | return self[left...right]
119 | }
120 |
121 | /// Allows to get a substring for a specified partial range.
122 | /// `String[1...]`
123 | ///
124 | /// - Parameter value: substring
125 | subscript(value: PartialRangeFrom) -> Substring? {
126 | guard let left = self.offset(by: value.lowerBound) else { return nil }
127 | return self[left...]
128 | }
129 |
130 | /// Allows to get a substring starting for a given number of characters.
131 | /// `String[...2]`
132 | ///
133 | /// - Parameter value: substring
134 | subscript(value: PartialRangeThrough) -> Substring? {
135 | guard let right = self.offset(by: value.upperBound) else { return nil }
136 | return self[...right]
137 | }
138 |
139 |
140 | internal func offset(by distance: Int) -> String.Index? {
141 | return index(startIndex, offsetBy: distance, limitedBy: endIndex)
142 | }
143 |
144 | /// Return a new string by removing given prefix.
145 | ///
146 | /// - Parameter prefix: prefix to remove.
147 | /// - Returns: new instance of the string without the prefix
148 | func removing(prefix: String) -> String {
149 | if hasPrefix(prefix) {
150 | let start = index(startIndex, offsetBy: prefix.count)
151 | // return substring(from: start)
152 | return self[start...].string
153 | }
154 | return self
155 | }
156 |
157 | func removing(suffix: String) -> String {
158 | if hasSuffix(suffix) {
159 | let end = index(startIndex, offsetBy: self.count-suffix.count)
160 | return self[.. 4.0)
180 |
181 | #if swift(>=4.1)
182 | #else
183 | extension Collection {
184 | func compactMap(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult] {
185 | return try flatMap(transform)
186 | }
187 | }
188 | #endif
189 |
--------------------------------------------------------------------------------
/Sources/SwiftRichString/Style/StyleGroup.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftRichString
3 | // Elegant Strings & Attributed Strings Toolkit for Swift
4 | //
5 | // Created by Daniele Margutti.
6 | // Copyright © 2018 Daniele Margutti. All rights reserved.
7 | //
8 | // Web: http://www.danielemargutti.com
9 | // Email: hello@danielemargutti.com
10 | // Twitter: @danielemargutti
11 | //
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in
21 | // all copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29 | // THE SOFTWARE.
30 |
31 | import Foundation
32 | #if os(OSX)
33 | import AppKit
34 | #else
35 | import UIKit
36 | #endif
37 |
38 | public typealias StyleGroup = StyleXML
39 |
40 | public class StyleXML: StyleProtocol {
41 |
42 | // The following attributes are ignored for StyleXML because are read from the sub styles.
43 | public var attributes: [NSAttributedString.Key : Any] = [:]
44 | public var fontData: FontData? = nil
45 | public var textTransforms: [TextTransform]? = nil
46 |
47 | /// Ordered dictionary of the styles inside the group
48 | public private(set) var styles: [String : StyleProtocol]
49 |
50 | /// Style to apply as base. If set, before parsing the string style is applied (add/set)
51 | /// to the existing source.
52 | public var baseStyle: StyleProtocol?
53 |
54 | /// XML Parsing options.
55 | /// By default `escapeString` is applied.
56 | public var xmlParsingOptions: XMLParsingOptions = [.escapeString]
57 |
58 | /// Image provider is called to provide custom image when `StyleXML` encounter a `img` tag image.
59 | /// If not implemented the image is automatically searched inside any bundled `xcassets`.
60 | public var imageProvider: ((_ name: String, _ attributes: [String: String]?) -> Image?)? = nil
61 |
62 | /// Dynamic attributes resolver.
63 | /// By default the `StandardXMLAttributesResolver` instance is used.
64 | public var xmlAttributesResolver: XMLDynamicAttributesResolver = StandardXMLAttributesResolver()
65 |
66 | // MARK: - Initialization
67 |
68 | /// Initialize a new `StyleXML` with a dictionary of style and names.
69 | /// Note: Ordered is not guarantee, use `init(_ styles:[(String, StyleProtocol)]` if you
70 | /// need to keep the order of the styles.
71 | ///
72 | /// - Parameters:
73 | /// - base: base style applied to the entire string.
74 | /// - styles: styles dictionary used to map your xml tags to styles definitions.
75 | public init(base: StyleProtocol? = nil, _ styles: [String: StyleProtocol] = [:]) {
76 | self.styles = styles
77 | self.baseStyle = base
78 | }
79 |
80 | // MARK: - Public Methods
81 |
82 | /// Append a new style with given name inside the list.
83 | /// Order is preserved.
84 | ///
85 | /// - Parameters:
86 | /// - style: style you want to add.
87 | /// - name: unique name of the style.
88 | public func add(style: Style, as name: String) {
89 | return self.styles[name] = style
90 | }
91 |
92 |
93 | /// Remove the style with given name.
94 | ///
95 | /// - Parameter name: name of the style to remove.
96 | /// - Returns: removed style instance.
97 | @discardableResult
98 | public func remove(style name: String) -> StyleProtocol? {
99 | return styles.removeValue(forKey: name)
100 | }
101 |
102 | //MARK: - Rendering Methods
103 |
104 | /// Render given source with styles defined inside the receiver.
105 | /// Styles are added as sum to any existing
106 | ///
107 | /// - Parameters:
108 | /// - source: source to render.
109 | /// - range: range of characters to render, `nil` to apply rendering to the entire content.
110 | /// - Returns: attributed string
111 | public func set(to source: String, range: NSRange?) -> AttributedString {
112 | let attributed = NSMutableAttributedString(string: source)
113 | return self.apply(to: attributed, adding: true, range: range)
114 | }
115 |
116 | /// Render given source string by appending parsed styles to the existing attributed string.
117 | ///
118 | /// - Parameters:
119 | /// - source: source attributed string.
120 | /// - range: range of parse.
121 | /// - Returns: same istance of `source` with applied styles.
122 | public func add(to source: AttributedString, range: NSRange?) -> AttributedString {
123 | return self.apply(to: source, adding: true, range: range)
124 | }
125 |
126 | /// Render given source string by replacing existing styles to parsed tags.
127 | ///
128 | /// - Parameters:
129 | /// - source: source attributed string.
130 | /// - range: range of parse.
131 | /// - Returns: same istance of `source` with applied styles.
132 | public func set(to source: AttributedString, range: NSRange?) -> AttributedString {
133 | return self.apply(to: source, adding: false, range: range)
134 | }
135 |
136 | /// Parse tags and render the attributed string with the styles defined into the receiver.
137 | ///
138 | /// - Parameters:
139 | /// - attrStr: source attributed string
140 | /// - adding: `true` to add styles defined to existing styles, `false` to replace any existing style inside tags.
141 | /// - range: range of operation, `nil` for entire string.
142 | /// - Returns: modified attributed string, same instance of the `source`.
143 | public func apply(to attrStr: AttributedString, adding: Bool, range: NSRange?) -> AttributedString {
144 | do {
145 | let xmlParser = XMLStringBuilder(styleXML: self, string: attrStr.string)
146 | return try xmlParser.parse()
147 | } catch {
148 | debugPrint("Failed to generate attributed string from xml: \(error)")
149 | return attrStr
150 | }
151 | }
152 |
153 | }
154 |
--------------------------------------------------------------------------------
/Sources/SwiftRichString/Style/StyleProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftRichString
3 | // Elegant Strings & Attributed Strings Toolkit for Swift
4 | //
5 | // Created by Daniele Margutti.
6 | // Copyright © 2018 Daniele Margutti. All rights reserved.
7 | //
8 | // Web: http://www.danielemargutti.com
9 | // Email: hello@danielemargutti.com
10 | // Twitter: @danielemargutti
11 | //
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in
21 | // all copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29 | // THE SOFTWARE.
30 |
31 | import Foundation
32 |
33 | public typealias AttributedString = NSMutableAttributedString
34 |
35 | public protocol StyleProtocol: class {
36 |
37 | /// Return the attributes of the style in form of dictionary `NSAttributedStringKey`/`Any`.
38 | var attributes: [NSAttributedString.Key : Any] { get }
39 |
40 | /// Font unique attributes dictionary.
41 | var fontData: FontData? { get }
42 |
43 | var textTransforms: [TextTransform]? { get }
44 |
45 | func set(to source: String, range: NSRange?) -> AttributedString
46 |
47 | func add(to source: AttributedString, range: NSRange?) -> AttributedString
48 |
49 | @discardableResult
50 | func set(to source: AttributedString, range: NSRange?) -> AttributedString
51 |
52 | @discardableResult
53 | func remove(from source: AttributedString, range: NSRange?) -> AttributedString
54 | }
55 |
56 | public extension StyleProtocol {
57 |
58 | func set(to source: String, range: NSRange?) -> AttributedString {
59 | let attributedText = NSMutableAttributedString(string: source)
60 | self.fontData?.addAttributes(to: attributedText, range: nil)
61 | attributedText.addAttributes(self.attributes, range: (range ?? NSMakeRange(0, attributedText.length)))
62 | return applyTextTransform(self.textTransforms, to: attributedText)
63 | }
64 |
65 | func add(to source: AttributedString, range: NSRange?) -> AttributedString {
66 | self.fontData?.addAttributes(to: source, range: range)
67 | source.addAttributes(self.attributes, range: (range ?? NSMakeRange(0, source.length)))
68 | return applyTextTransform(self.textTransforms, to: source)
69 | }
70 |
71 | @discardableResult
72 | func set(to source: AttributedString, range: NSRange?) -> AttributedString {
73 | self.fontData?.addAttributes(to: source, range: range)
74 | source.addAttributes(self.attributes, range: (range ?? NSMakeRange(0, source.length)))
75 | return applyTextTransform(self.textTransforms, to: source)
76 | }
77 |
78 | @discardableResult
79 | func remove(from source: AttributedString, range: NSRange?) -> AttributedString {
80 | self.attributes.keys.forEach({
81 | source.removeAttribute($0, range: (range ?? NSMakeRange(0, source.length)))
82 | })
83 | return applyTextTransform(self.textTransforms, to: source)
84 | }
85 |
86 | private func applyTextTransform(_ transforms: [TextTransform]?, to string: AttributedString) -> AttributedString {
87 | guard let transforms = self.textTransforms else {
88 | return string
89 | }
90 |
91 | let mutable = string.mutableStringCopy()
92 | let fullRange = NSRange(location: 0, length: mutable.length)
93 | mutable.enumerateAttributes(in: fullRange, options: [], using: { (_, range, _) in
94 | var substring = mutable.attributedSubstring(from: range).string
95 | transforms.forEach {
96 | substring = $0.transformer(substring)
97 | }
98 | mutable.replaceCharacters(in: range, with: substring)
99 | })
100 |
101 | return mutable
102 | }
103 |
104 | }
105 |
--------------------------------------------------------------------------------
/Sources/SwiftRichString/Style/StyleRegEx.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftRichString
3 | // Elegant Strings & Attributed Strings Toolkit for Swift
4 | //
5 | // Created by Daniele Margutti.
6 | // Copyright © 2018 Daniele Margutti. All rights reserved.
7 | //
8 | // Web: http://www.danielemargutti.com
9 | // Email: hello@danielemargutti.com
10 | // Twitter: @danielemargutti
11 | //
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in
21 | // all copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29 | // THE SOFTWARE.
30 |
31 | import Foundation
32 | #if os(OSX)
33 | import AppKit
34 | #else
35 | import UIKit
36 | #endif
37 |
38 | /// StyleRegEx allows you to define a style which is applied when one or more regular expressions
39 | /// matches are found in source string or attributed string.
40 | public class StyleRegEx: StyleProtocol {
41 |
42 | //MARK: - PROPERTIES
43 |
44 | /// Regular expression
45 | public private(set) var regex: NSRegularExpression
46 |
47 | /// Base style. If set it will be applied in set before any match.
48 | public private(set) var baseStyle: StyleProtocol?
49 |
50 | /// Applied style
51 | private var style: StyleProtocol
52 |
53 | /// Style attributes
54 | public var attributes: [NSAttributedString.Key : Any] {
55 | return self.style.attributes
56 | }
57 |
58 | public var textTransforms: [TextTransform]?
59 |
60 | /// Font attributes
61 | public var fontData: FontData? {
62 | return self.style.fontData
63 | }
64 |
65 | //MARK: - INIT
66 |
67 | /// Initialize a new regular expression style matcher.
68 | ///
69 | /// - Parameters:
70 | /// - base: base style. it will be applied (in set or add) to the entire string before any other operation.
71 | /// - pattern: pattern of regular expression.
72 | /// - options: matching options of the regular expression; if not specified `caseInsensitive` is used.
73 | /// - handler: configuration handler for style.
74 | public init?(base: StyleProtocol? = nil,
75 | pattern: String, options: NSRegularExpression.Options = .caseInsensitive,
76 | handler: @escaping Style.StyleInitHandler) {
77 | do {
78 | self.regex = try NSRegularExpression(pattern: pattern, options: options)
79 | self.baseStyle = base
80 | self.style = Style(handler)
81 | } catch {
82 | return nil
83 | }
84 | }
85 |
86 | //MARK: - METHOD OVERRIDES
87 |
88 | public func set(to source: String, range: NSRange?) -> AttributedString {
89 | let attributed = NSMutableAttributedString(string: source, attributes: (self.baseStyle?.attributes ?? [:]))
90 | return self.applyStyle(to: attributed, add: false, range: range)
91 | }
92 |
93 | public func add(to source: AttributedString, range: NSRange?) -> AttributedString {
94 | if let base = self.baseStyle {
95 | source.addAttributes(base.attributes, range: (range ?? NSMakeRange(0, source.length)))
96 | }
97 | return self.applyStyle(to: source, add: true, range: range)
98 | }
99 |
100 | public func set(to source: AttributedString, range: NSRange?) -> AttributedString {
101 | if let base = self.baseStyle {
102 | source.setAttributes(base.attributes, range: (range ?? NSMakeRange(0, source.length)))
103 | }
104 | return self.applyStyle(to: source, add: false, range: range)
105 | }
106 |
107 | //MARK: - INTERNAL FUNCTIONS
108 |
109 | /// Regular expression matcher.
110 | ///
111 | /// - Parameters:
112 | /// - str: attributed string.
113 | /// - add: `true` to append styles, `false` to replace existing styles.
114 | /// - range: range, `nil` to apply the style to entire string.
115 | /// - Returns: modified attributed string
116 | private func applyStyle(to str: AttributedString, add: Bool, range: NSRange?) -> AttributedString {
117 | let rangeValue = (range ?? NSMakeRange(0, str.length))
118 |
119 | let matchOpts = NSRegularExpression.MatchingOptions(rawValue: 0)
120 | self.regex.enumerateMatches(in: str.string, options: matchOpts, range: rangeValue) {
121 | (result : NSTextCheckingResult?, _, _) in
122 | if let r = result {
123 | if add {
124 | str.addAttributes(self.attributes, range: r.range)
125 | } else {
126 | str.setAttributes(self.attributes, range: r.range)
127 | }
128 | }
129 | }
130 |
131 | return str
132 | }
133 |
134 | }
135 |
--------------------------------------------------------------------------------
/Sources/SwiftRichString/Style/StylesManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftRichString
3 | // Elegant Strings & Attributed Strings Toolkit for Swift
4 | //
5 | // Created by Daniele Margutti.
6 | // Copyright © 2018 Daniele Margutti. All rights reserved.
7 | //
8 | // Web: http://www.danielemargutti.com
9 | // Email: hello@danielemargutti.com
10 | // Twitter: @danielemargutti
11 | //
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in
21 | // all copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29 | // THE SOFTWARE.
30 |
31 | import Foundation
32 |
33 | /// Shortcut to singleton of the `StylesManager`
34 | public let Styles: StylesManager = StylesManager.shared
35 |
36 | /// StylesManager act as a central repository where you can register your own style and use
37 | /// globally in your app.
38 | public class StylesManager {
39 |
40 | /// Singleton instance.
41 | public static let shared: StylesManager = StylesManager()
42 |
43 | /// You can defeer the creation of a style to the first time its required.
44 | /// Implementing this method you will receive a call with the name of the style
45 | /// you are about to provide and you have a chance to make and return it.
46 | /// Once returned the style is automatically cached.
47 | public var onDeferStyle: ((String) -> (StyleProtocol?, Bool))? = nil
48 |
49 | /// Registered styles.
50 | public private(set) var styles: [String: StyleProtocol] = [:]
51 |
52 | /// Register a new style with given name.
53 | ///
54 | /// - Parameters:
55 | /// - name: unique identifier of style.
56 | /// - style: style to register.
57 | public func register(_ name: String, style: StyleProtocol) {
58 | self.styles[name] = style
59 | }
60 |
61 | /// Return a style registered with given name.
62 | ///
63 | /// - Parameter name: name of the style
64 | public subscript(name: String?) -> StyleProtocol? {
65 | guard let name = name else { return nil }
66 |
67 | if let cachedStyle = self.styles[name] { // style is cached
68 | return cachedStyle
69 | } else {
70 | // check if user can provide a deferred creation for this style
71 | if let (deferredStyle,canCache) = self.onDeferStyle?(name) {
72 | // cache if requested
73 | if canCache, let dStyle = deferredStyle { self.styles[name] = dStyle }
74 | return deferredStyle
75 | }
76 | return nil // nothing
77 | }
78 | }
79 |
80 | /// Return a list of styles registered with given names.
81 | ///
82 | /// - Parameter names: array of style's name to get.
83 | public subscript(names: [String]?) -> [StyleProtocol]? {
84 | guard let names = names else { return nil }
85 | return names.compactMap { self.styles[$0] }
86 | }
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/Sources/SwiftRichString/Support/AssociatedValues.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftRichString
3 | // Elegant Strings & Attributed Strings Toolkit for Swift
4 | //
5 | // Created by Daniele Margutti.
6 | // Copyright © 2018 Daniele Margutti. All rights reserved.
7 | //
8 | // Web: http://www.danielemargutti.com
9 | // Email: hello@danielemargutti.com
10 | // Twitter: @danielemargutti
11 | //
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in
21 | // all copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29 | // THE SOFTWARE.
30 |
31 | import Foundation
32 | import ObjectiveC.runtime
33 |
34 | internal func getAssociatedValue(key: String, object: AnyObject) -> T? {
35 | return (objc_getAssociatedObject(object, key.address) as? AssociatedValue)?.value as? T
36 | }
37 |
38 | internal func getAssociatedValue(key: String, object: AnyObject, initialValue: @autoclosure () -> T) -> T {
39 | return getAssociatedValue(key: key, object: object) ?? setAndReturn(initialValue: initialValue(), key: key, object: object)
40 | }
41 |
42 | internal func getAssociatedValue(key: String, object: AnyObject, initialValue: () -> T) -> T {
43 | return getAssociatedValue(key: key, object: object) ?? setAndReturn(initialValue: initialValue(), key: key, object: object)
44 | }
45 |
46 | fileprivate func setAndReturn(initialValue: T, key: String, object: AnyObject) -> T {
47 | set(associatedValue: initialValue, key: key, object: object)
48 | return initialValue
49 | }
50 |
51 | internal func set(associatedValue: T?, key: String, object: AnyObject) {
52 | set(associatedValue: AssociatedValue(associatedValue), key: key, object: object)
53 | }
54 |
55 | internal func set(weakAssociatedValue: T?, key: String, object: AnyObject) {
56 | set(associatedValue: AssociatedValue(weak: weakAssociatedValue), key: key, object: object)
57 | }
58 |
59 | extension String {
60 |
61 | fileprivate var address: UnsafeRawPointer {
62 | return UnsafeRawPointer(bitPattern: abs(hashValue))!
63 | }
64 |
65 | }
66 |
67 | private func set(associatedValue: AssociatedValue, key: String, object: AnyObject) {
68 | objc_setAssociatedObject(object, key.address, associatedValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
69 | }
70 |
71 | private class AssociatedValue {
72 |
73 | weak var _weakValue: AnyObject?
74 | var _value: Any?
75 |
76 | var value: Any? {
77 | return _weakValue ?? _value
78 | }
79 |
80 | init(_ value: Any?) {
81 | _value = value
82 | }
83 |
84 | init(weak: AnyObject?) {
85 | _weakValue = weak
86 | }
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/Sources/SwiftRichString/Support/AsyncTextAttachment.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftRichString
3 | // Elegant Strings & Attributed Strings Toolkit for Swift
4 | //
5 | // Created by Daniele Margutti.
6 | // Copyright © 2018 Daniele Margutti. All rights reserved.
7 | //
8 | // Web: http://www.danielemargutti.com
9 | // Email: hello@danielemargutti.com
10 | // Twitter: @danielemargutti
11 | //
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in
21 | // all copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29 | // THE SOFTWARE.
30 |
31 | #if os(OSX)
32 | import AppKit
33 | #else
34 | import UIKit
35 | import MobileCoreServices
36 | #endif
37 |
38 | #if os(iOS)
39 |
40 | @objc public protocol AsyncTextAttachmentDelegate
41 | {
42 | /// Called when the image has been loaded
43 | func textAttachmentDidLoadImage(textAttachment: AsyncTextAttachment, displaySizeChanged: Bool)
44 | }
45 |
46 | /// An image text attachment that gets loaded from a remote URL
47 | public class AsyncTextAttachment: NSTextAttachment {
48 | /// Remote URL for the image
49 | public var imageURL: URL?
50 |
51 | /// To specify an absolute display size.
52 | public var displaySize: CGSize?
53 |
54 | /// if determining the display size automatically this can be used to specify a maximum width. If it is not set then the text container's width will be used
55 | public var maximumDisplayWidth: CGFloat?
56 |
57 | /// A delegate to be informed of the finished download
58 | public weak var delegate: AsyncTextAttachmentDelegate?
59 |
60 | /// Remember the text container from delegate message, the current one gets updated after the download
61 | weak var textContainer: NSTextContainer?
62 |
63 | /// The download task to keep track of whether we are already downloading the image
64 | private var downloadTask: URLSessionDataTask!
65 |
66 | /// The size of the downloaded image. Used if we need to determine display size
67 | private var originalImageSize: CGSize?
68 |
69 | /// Designated initializer
70 | public init(imageURL: URL? = nil, delegate: AsyncTextAttachmentDelegate? = nil) {
71 | self.imageURL = imageURL
72 | self.delegate = delegate
73 |
74 | super.init(data: nil, ofType: nil)
75 | }
76 |
77 | required public init?(coder aDecoder: NSCoder) {
78 | fatalError("init(coder:) has not been implemented")
79 | }
80 |
81 | override public var image: UIImage? {
82 | didSet {
83 | originalImageSize = image?.size
84 | }
85 | }
86 |
87 | // MARK: - Helpers
88 |
89 | private func startAsyncImageDownload() {
90 | guard let imageURL = imageURL, contents == nil, downloadTask == nil else {
91 | return
92 | }
93 |
94 | downloadTask = URLSession.shared.dataTask(with: imageURL) { (data, response, error) in
95 |
96 | defer {
97 | // done with the task
98 | self.downloadTask = nil
99 | }
100 |
101 | guard let data = data, error == nil else {
102 | print(error?.localizedDescription as Any)
103 | return
104 | }
105 |
106 | var displaySizeChanged = false
107 |
108 | self.contents = data
109 |
110 | let ext = imageURL.pathExtension as CFString
111 | if let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, ext, nil) {
112 | self.fileType = uti.takeRetainedValue() as String
113 | }
114 |
115 | if let image = UIImage(data: data) {
116 | let imageSize = image.size
117 |
118 | if self.displaySize == nil
119 | {
120 | displaySizeChanged = true
121 | }
122 |
123 | self.originalImageSize = imageSize
124 | }
125 |
126 | DispatchQueue.main.async {
127 | // tell layout manager so that it should refresh
128 | if displaySizeChanged {
129 | self.textContainer?.layoutManager?.setNeedsLayout(forAttachment: self)
130 | } else {
131 | self.textContainer?.layoutManager?.setNeedsDisplay(forAttachment: self)
132 | }
133 |
134 | // notify the optional delegate
135 | self.delegate?.textAttachmentDidLoadImage(textAttachment: self, displaySizeChanged: displaySizeChanged)
136 | }
137 | }
138 |
139 | downloadTask.resume()
140 | }
141 |
142 | public override func image(forBounds imageBounds: CGRect, textContainer: NSTextContainer?, characterIndex charIndex: Int) -> UIImage? {
143 | if let image = image { return image }
144 |
145 | guard let contents = contents, let image = UIImage(data: contents) else {
146 | // remember reference so that we can update it later
147 | self.textContainer = textContainer
148 |
149 | startAsyncImageDownload()
150 |
151 | return nil
152 | }
153 |
154 | return image
155 | }
156 |
157 | public override func attachmentBounds(for textContainer: NSTextContainer?, proposedLineFragment lineFrag: CGRect, glyphPosition position: CGPoint, characterIndex charIndex: Int) -> CGRect {
158 | if let displaySize = displaySize {
159 | return CGRect(origin: CGPoint.zero, size: displaySize)
160 | }
161 |
162 | if let imageSize = originalImageSize {
163 | let maxWidth = maximumDisplayWidth ?? lineFrag.size.width
164 | let factor = maxWidth / imageSize.width
165 |
166 | return CGRect(origin: CGPoint.zero, size:CGSize(width: Int(imageSize.width * factor), height: Int(imageSize.height * factor)))
167 | }
168 |
169 | return CGRect.zero
170 | }
171 | }
172 |
173 |
174 | extension NSLayoutManager
175 | {
176 | /// Determine the character ranges for an attachment
177 | private func rangesForAttachment(attachment: NSTextAttachment) -> [NSRange]?
178 | {
179 | guard let attributedString = self.textStorage else
180 | {
181 | return nil
182 | }
183 |
184 | // find character range for this attachment
185 | let range = NSRange(location: 0, length: attributedString.length)
186 |
187 | var refreshRanges = [NSRange]()
188 |
189 | attributedString.enumerateAttribute(NSAttributedString.Key.attachment, in: range, options: []) { (value, effectiveRange, nil) in
190 |
191 | guard let foundAttachment = value as? NSTextAttachment, foundAttachment == attachment else
192 | {
193 | return
194 | }
195 |
196 | // add this range to the refresh ranges
197 | refreshRanges.append(effectiveRange)
198 | }
199 |
200 | if refreshRanges.count == 0
201 | {
202 | return nil
203 | }
204 |
205 | return refreshRanges
206 | }
207 |
208 | /// Trigger a relayout for an attachment
209 | public func setNeedsLayout(forAttachment attachment: NSTextAttachment)
210 | {
211 | guard let ranges = rangesForAttachment(attachment: attachment) else
212 | {
213 | return
214 | }
215 |
216 | // invalidate the display for the corresponding ranges
217 | for range in ranges.reversed() {
218 | self.invalidateLayout(forCharacterRange: range, actualCharacterRange: nil)
219 |
220 | // also need to trigger re-display or already visible images might not get updated
221 | self.invalidateDisplay(forCharacterRange: range)
222 | }
223 | }
224 |
225 | /// Trigger a re-display for an attachment
226 | public func setNeedsDisplay(forAttachment attachment: NSTextAttachment)
227 | {
228 | guard let ranges = rangesForAttachment(attachment: attachment) else
229 | {
230 | return
231 | }
232 |
233 | // invalidate the display for the corresponding ranges
234 | for range in ranges.reversed() {
235 | self.invalidateDisplay(forCharacterRange: range)
236 | }
237 | }
238 | }
239 |
240 | #endif
241 |
--------------------------------------------------------------------------------
/Sources/SwiftRichString/Support/Compatibility.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftRichString
3 | // Elegant Strings & Attributed Strings Toolkit for Swift
4 | //
5 | // Created by Daniele Margutti.
6 | // Copyright © 2018 Daniele Margutti. All rights reserved.
7 | //
8 | // Web: http://www.danielemargutti.com
9 | // Email: hello@danielemargutti.com
10 | // Twitter: @danielemargutti
11 | //
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in
21 | // all copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29 | // THE SOFTWARE.
30 |
31 | import Foundation
32 | #if os(OSX)
33 | import AppKit
34 | #else
35 | import UIKit
36 | #endif
37 |
38 | #if os(OSX)
39 | #if swift(>=4.2)
40 | #else
41 | public typealias NSLineBreakMode = NSParagraphStyle.LineBreakMode
42 | #endif
43 | #endif
44 |
45 | // Helper function inserted by Swift 4.2 migrator.
46 | func convertFromNSAttributedStringKey(_ input: NSAttributedString.Key) -> String {
47 | return input.rawValue
48 | }
49 |
50 |
51 | #if swift(>=4.2)
52 | #else
53 | extension NSAttributedString {
54 | public typealias Key = NSAttributedStringKey
55 | }
56 | #endif
57 |
58 | #if os(iOS) || os(tvOS) || os(watchOS)
59 |
60 | extension NSAttributedString.Key {
61 | #if swift(>=4.2)
62 | #else
63 | static let accessibilitySpeechPunctuation = NSAttributedString.Key(UIAccessibilitySpeechAttributePunctuation)
64 | static let accessibilitySpeechLanguage = NSAttributedString.Key(UIAccessibilitySpeechAttributeLanguage)
65 | static let accessibilitySpeechPitch = NSAttributedString.Key(UIAccessibilitySpeechAttributePitch)
66 |
67 | @available(iOS 11.0, tvOS 11.0, watchOS 4.0, *)
68 | static let accessibilitySpeechIPANotation = NSAttributedString.Key(UIAccessibilitySpeechAttributeIPANotation)
69 |
70 | @available(iOS 11.0, tvOS 11.0, watchOS 4.0, *)
71 | static let accessibilitySpeechQueueAnnouncement = NSAttributedString.Key(UIAccessibilitySpeechAttributeQueueAnnouncement)
72 |
73 | @available(iOS 11.0, tvOS 11.0, watchOS 4.0, *)
74 | static let accessibilityTextHeadingLevel = NSAttributedString.Key(UIAccessibilityTextAttributeHeadingLevel)
75 | #endif
76 | }
77 |
78 | #endif
79 |
--------------------------------------------------------------------------------
/Sources/SwiftRichString/Support/Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftRichString
3 | // Elegant Strings & Attributed Strings Toolkit for Swift
4 | //
5 | // Created by Daniele Margutti.
6 | // Copyright © 2018 Daniele Margutti. All rights reserved.
7 | //
8 | // Web: http://www.danielemargutti.com
9 | // Email: hello@danielemargutti.com
10 | // Twitter: @danielemargutti
11 | //
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in
21 | // all copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29 | // THE SOFTWARE.
30 |
31 | import Foundation
32 | #if os(OSX)
33 | import AppKit
34 | #else
35 | import UIKit
36 | #endif
37 |
38 | extension String {
39 |
40 | private static let escapeAmpRegExp = try! NSRegularExpression(pattern: "&(?!(#[0-9]{2,4}|[A-z]{2,6});)", options: NSRegularExpression.Options(rawValue: 0))
41 |
42 | public func escapeWithUnicodeEntities() -> String {
43 | let range = NSRange(location: 0, length: self.count)
44 | return String.escapeAmpRegExp.stringByReplacingMatches(in: self,
45 | options: NSRegularExpression.MatchingOptions(rawValue: 0),
46 | range: range,
47 | withTemplate: "&")
48 | }
49 |
50 | }
51 |
52 | extension NSNumber {
53 |
54 | internal static func from(float: Float?) -> NSNumber? {
55 | guard let float = float else { return nil }
56 | return NSNumber(value: float)
57 | }
58 |
59 | internal static func from(int: Int?) -> NSNumber? {
60 | guard let int = int else { return nil }
61 | return NSNumber(value: int)
62 | }
63 |
64 | internal static func from(underlineStyle: NSUnderlineStyle?) -> NSNumber? {
65 | guard let v = underlineStyle?.rawValue else { return nil }
66 | return NSNumber(value: v)
67 | }
68 |
69 | internal func toUnderlineStyle() -> NSUnderlineStyle? {
70 | return NSUnderlineStyle.init(rawValue: self.intValue)
71 | }
72 |
73 | }
74 |
75 | extension NSAttributedString {
76 |
77 | @nonobjc func mutableStringCopy() -> NSMutableAttributedString {
78 | guard let copy = mutableCopy() as? NSMutableAttributedString else {
79 | fatalError("Failed to mutableCopy() \(self)")
80 | }
81 | return copy
82 | }
83 |
84 | }
85 |
86 | public extension Array where Array.Element == StyleProtocol {
87 |
88 | /// Merge styles from array of `StyleProtocol` elements.
89 | /// Merge is made in order where each n+1 elements may replace existing keys defined by n-1 elements.
90 | ///
91 | /// - Returns: merged style
92 | func mergeStyle() -> Style {
93 | var attributes: [NSAttributedString.Key:Any] = [:]
94 | var textTransforms = [TextTransform]()
95 | self.forEach {
96 | attributes.merge($0.attributes, uniquingKeysWith: {
97 | (_, new) in
98 | return new
99 | })
100 | textTransforms.append(contentsOf: $0.textTransforms ?? [])
101 | }
102 | return Style(dictionary: attributes, textTransforms: (textTransforms.isEmpty ? nil : textTransforms))
103 | }
104 |
105 | }
106 |
107 | extension CGRect {
108 |
109 | init?(string: String?) {
110 | guard let string = string else {
111 | return nil
112 | }
113 |
114 | let components: [CGFloat] = string.components(separatedBy: ",").compactMap {
115 | guard let value = Float($0) else { return nil }
116 | return CGFloat(value)
117 | }
118 |
119 | guard components.count == 4 else {
120 | return nil
121 | }
122 |
123 | self = CGRect(x: components[0],
124 | y: components[1],
125 | width: components[2],
126 | height: components[3])
127 | }
128 |
129 | }
130 |
131 | #if os(OSX)
132 |
133 | public extension NSImage {
134 |
135 | /// PNG data of the image.
136 | func pngData() -> Data? {
137 | self.lockFocus()
138 | let bitmap = NSBitmapImageRep(focusedViewRect: NSRect(x: 0, y: 0, width: size.width, height: size.height))
139 | let pngData = bitmap!.representation(using: .png, properties: [:])
140 | self.unlockFocus()
141 | return pngData
142 | }
143 |
144 | }
145 |
146 | #endif
147 |
--------------------------------------------------------------------------------
/Sources/SwiftRichString/Support/OrderedDictionary.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftRichString
3 | // Elegant Strings & Attributed Strings Toolkit for Swift
4 | //
5 | // Created by Daniele Margutti.
6 | // Copyright © 2018 Daniele Margutti. All rights reserved.
7 | //
8 | // Web: http://www.danielemargutti.com
9 | // Email: hello@danielemargutti.com
10 | // Twitter: @danielemargutti
11 | //
12 | //
13 | // Permission is hereby granted, free of charge, to any person obtaining a copy
14 | // of this software and associated documentation files (the "Software"), to deal
15 | // in the Software without restriction, including without limitation the rights
16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 | // copies of the Software, and to permit persons to whom the Software is
18 | // furnished to do so, subject to the following conditions:
19 | //
20 | // The above copyright notice and this permission notice shall be included in
21 | // all copies or substantial portions of the Software.
22 | //
23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29 | // THE SOFTWARE.
30 |
31 | import Foundation
32 |
33 | public struct OrderedDictionary {
34 | var keys = [K]()
35 | var dict = [K:V]()
36 |
37 | public var count: Int {
38 | return self.keys.count
39 | }
40 |
41 | public subscript(key: K) -> V? {
42 | get {
43 | return self.dict[key]
44 | }
45 | set(newValue) {
46 | if newValue == nil {
47 | self.dict.removeValue(forKey:key)
48 | self.keys = self.keys.filter {$0 != key}
49 | } else {
50 | let oldValue = self.dict.updateValue(newValue!, forKey: key)
51 | if oldValue == nil {
52 | self.keys.append(key)
53 | }
54 | }
55 | }
56 | }
57 |
58 | public mutating func remove(key: K) -> V? {
59 | guard let idx = self.keys.firstIndex(of: key) else { return nil }
60 | self.keys.remove(at: idx)
61 | return self.dict.removeValue(forKey: key)
62 | }
63 | }
64 |
65 | extension OrderedDictionary: Sequence {
66 | public func makeIterator() -> AnyIterator {
67 | var counter = 0
68 | return AnyIterator {
69 | guard counter Image?
51 |
52 | /// You are receiving this event when SwiftRichString correctly render an existing tag but the tag
53 | /// contains extra attributes you may want to handle.
54 | /// For example you can pass a specific tag `text/` and you want to override
55 | /// the color with passed value in tags.
56 | ///
57 | /// - Parameters:
58 | /// - attributedString: attributed string. You will receive it after the style is applied.
59 | /// - xmlStyle: xml style information with tag, applied style and the dictionary with extra attributes.
60 | /// - fromStyle: caller instance of `StyleXML.
61 | func applyDynamicAttributes(to attributedString: inout AttributedString, xmlStyle: XMLDynamicStyle, fromStyle: StyleXML)
62 |
63 | /// You will receive this event when SwiftRichString can't found a received style name into provided group tags.
64 | /// You can decide to handle it. The default receiver for example uses the `a` tag to render passed url if `href`
65 | /// attribute is alo present.
66 | ///
67 | /// - Parameters:
68 | /// - tag: tag name received.
69 | /// - attributedString: attributed string received.
70 | /// - attributes: attributes of the tag received.
71 | /// - fromStyle: caller instance of `StyleXML.
72 | func styleForUnknownXMLTag(_ tag: String, to attributedString: inout AttributedString, attributes: [String: String]?, fromStyle: StyleXML)
73 |
74 | }
75 |
76 | extension XMLDynamicAttributesResolver {
77 |
78 | public func image(name: String, attributes: [String: String]?, fromStyle style: StyleXML) -> Image? {
79 | guard let mappedImage = style.imageProvider?(name, attributes) else {
80 | return Image(named: name) // xcassets fallback
81 | }
82 |
83 | // origin xml style contains mapped image.
84 | return mappedImage
85 | }
86 |
87 | }
88 |
89 | // MARK: - StandardXMLAttributesResolver
90 |
91 | open class StandardXMLAttributesResolver: XMLDynamicAttributesResolver {
92 |
93 | public init() {}
94 |
95 | open func applyDynamicAttributes(to attributedString: inout AttributedString, xmlStyle: XMLDynamicStyle, fromStyle: StyleXML) {
96 | let finalStyleToApply = Style()
97 | xmlStyle.enumerateAttributes { key, value in
98 | switch key {
99 | case "color": // color support
100 | finalStyleToApply.color = Color(hexString: value)
101 |
102 | default: break
103 | }
104 | }
105 | self.styleForUnknownXMLTag(xmlStyle.tag, to: &attributedString, attributes: xmlStyle.xmlAttributes, fromStyle: fromStyle)
106 | attributedString.add(style: finalStyleToApply)
107 | }
108 |
109 | open func styleForUnknownXMLTag(_ tag: String, to attributedString: inout AttributedString, attributes: [String: String]?, fromStyle: StyleXML) {
110 | let finalStyleToApply = Style()
111 | switch tag {
112 | case "a": // href support
113 | finalStyleToApply.linkURL = URL(string: attributes?["href"])
114 |
115 | case "img":
116 | #if os(iOS)
117 | // Remote Image URL support
118 | if let url = attributes?["url"] {
119 | if let image = AttributedString(imageURL: url, bounds: attributes?["rect"]) {
120 | attributedString.append(image)
121 | }
122 | }
123 | #endif
124 |
125 | #if os(iOS) || os(OSX)
126 | // Local Image support
127 | if let imageName = attributes?["named"] {
128 | if let image = image(name: imageName, attributes: attributes, fromStyle: fromStyle),
129 | let imageString = AttributedString(image: image, bounds: attributes?["rect"]) {
130 | attributedString.append(imageString)
131 | }
132 | }
133 | #endif
134 |
135 | default:
136 | break
137 | }
138 | attributedString.add(style: finalStyleToApply)
139 | }
140 |
141 | }
142 |
--------------------------------------------------------------------------------
/SwiftRichString.playground/Contents.swift:
--------------------------------------------------------------------------------
1 |
2 | import UIKit
3 | import SwiftRichString
4 | import PlaygroundSupport
5 |
6 | // Create your own styles
7 |
8 | let normal = Style {
9 | $0.font = SystemFonts.Helvetica_Light.font(size: 15)
10 | }
11 |
12 | let bold = Style {
13 | $0.font = SystemFonts.Helvetica_Bold.font(size: 20)
14 | $0.color = UIColor.red
15 | $0.backColor = UIColor.yellow
16 | }
17 |
18 | let italic = normal.byAdding {
19 | $0.traitVariants = .italic
20 | }
21 |
22 | // Create a group which contains your style, each identified by a tag.
23 | let myGroup = StyleGroup(base: normal, ["bold": bold, "italic": italic])
24 |
25 | // Use tags in your plain string
26 | let str = "Hello Daniele! . You're ready to play with us! "
27 |
28 | let attributedStringController = AttributedStringController()
29 | PlaygroundPage.current.liveView = attributedStringController
30 |
31 | attributedStringController.attributedString = str.set(style: myGroup)
32 |
33 |
34 |
--------------------------------------------------------------------------------
/SwiftRichString.playground/Sources/AttributedStringController.swift:
--------------------------------------------------------------------------------
1 |
2 | import UIKit
3 |
4 | public class AttributedStringController: UIViewController {
5 |
6 | public var attributedString: NSAttributedString? {
7 | get {
8 | return textView?.attributedText
9 | }
10 | set {
11 | textView?.attributedText = newValue
12 | }
13 | }
14 |
15 | private var textView: UITextView?
16 |
17 | public override func loadView() {
18 | let view = UIView()
19 | view.backgroundColor = .white
20 |
21 | let textView = UITextView()
22 | textView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
23 | textView.attributedText = attributedString
24 | view.addSubview(textView)
25 | self.textView = textView
26 |
27 | self.view = view
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/SwiftRichString.playground/contents.xcplayground:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/SwiftRichString.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = "SwiftRichString"
3 | s.version = "3.7.2"
4 | s.summary = "Elegant Strings & Attributed Strings Toolkit for Swift"
5 | s.description = <<-DESC
6 | SwiftRichString is the best toolkit to work easily with Strings and Attributed Strings.
7 | DESC
8 | s.homepage = "https://github.com/malcommac/SwiftRichString"
9 | s.license = { :type => "MIT", :file => "LICENSE" }
10 | s.author = { "Daniele Margutti" => "hello@danielemargutti.com" }
11 | s.social_media_url = "https://twitter.com/danielemargutti"
12 | s.ios.deployment_target = "9.0"
13 | s.osx.deployment_target = "10.11"
14 | s.watchos.deployment_target = "2.0"
15 | s.tvos.deployment_target = "9.2"
16 | s.source = { :git => "https://github.com/malcommac/SwiftRichString.git", :tag => s.version.to_s }
17 | s.source_files = "Sources/**/*"
18 | s.frameworks = "Foundation"
19 | s.swift_versions = ['5.1']
20 | end
21 |
--------------------------------------------------------------------------------
/SwiftRichString.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/SwiftRichString.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/SwiftRichString.xcodeproj/project.xcworkspace/xcuserdata/daniele.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/malcommac/SwiftRichString/e0b72d5c96968d7802856d2be096202c9798e8d1/SwiftRichString.xcodeproj/project.xcworkspace/xcuserdata/daniele.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/SwiftRichString.xcodeproj/xcshareddata/xcschemes/SwiftRichString-iOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
38 |
39 |
40 |
41 |
43 |
49 |
50 |
51 |
52 |
53 |
63 |
64 |
70 |
71 |
72 |
73 |
79 |
80 |
86 |
87 |
88 |
89 |
91 |
92 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/SwiftRichString.xcodeproj/xcshareddata/xcschemes/SwiftRichString-macOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
38 |
39 |
40 |
41 |
43 |
49 |
50 |
51 |
52 |
53 |
63 |
64 |
70 |
71 |
72 |
73 |
79 |
80 |
86 |
87 |
88 |
89 |
91 |
92 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/SwiftRichString.xcodeproj/xcshareddata/xcschemes/SwiftRichString-tvOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
38 |
39 |
40 |
41 |
43 |
49 |
50 |
51 |
52 |
53 |
63 |
64 |
70 |
71 |
72 |
73 |
79 |
80 |
86 |
87 |
88 |
89 |
91 |
92 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/SwiftRichString.xcodeproj/xcshareddata/xcschemes/SwiftRichString-watchOS.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
33 |
34 |
44 |
45 |
51 |
52 |
53 |
54 |
60 |
61 |
67 |
68 |
69 |
70 |
72 |
73 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/SwiftRichString.xcodeproj/xcuserdata/daniele.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
9 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/SwiftRichString.xcodeproj/xcuserdata/daniele.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | ExampleMac.xcscheme
8 |
9 | orderHint
10 | 5
11 |
12 | ExampleMac.xcscheme_^#shared#^_
13 |
14 | orderHint
15 | 5
16 |
17 | ExampleTvOS.xcscheme
18 |
19 | orderHint
20 | 6
21 |
22 | ExampleTvOS.xcscheme_^#shared#^_
23 |
24 | orderHint
25 | 6
26 |
27 | ExampleWatchOS (Notification).xcscheme
28 |
29 | orderHint
30 | 8
31 |
32 | ExampleWatchOS (Notification).xcscheme_^#shared#^_
33 |
34 | orderHint
35 | 8
36 |
37 | ExampleWatchOS.xcscheme
38 |
39 | orderHint
40 | 7
41 |
42 | ExampleWatchOS.xcscheme_^#shared#^_
43 |
44 | orderHint
45 | 7
46 |
47 | ExampleiOS.xcscheme
48 |
49 | orderHint
50 | 4
51 |
52 | ExampleiOS.xcscheme_^#shared#^_
53 |
54 | orderHint
55 | 4
56 |
57 | SwiftRichString-iOS.xcscheme_^#shared#^_
58 |
59 | orderHint
60 | 0
61 |
62 | SwiftRichString-macOS.xcscheme_^#shared#^_
63 |
64 | orderHint
65 | 1
66 |
67 | SwiftRichString-tvOS.xcscheme_^#shared#^_
68 |
69 | orderHint
70 | 2
71 |
72 | SwiftRichString-watchOS.xcscheme_^#shared#^_
73 |
74 | orderHint
75 | 3
76 |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/SwiftRichString.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/SwiftRichString.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import SwiftRichStringTests
3 |
4 | XCTMain([
5 | testCase(SwiftRichStringTests.allTests),
6 | ])
7 |
--------------------------------------------------------------------------------
/Tests/SwiftRichStringTests/SwiftRichStringTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftRichStringTests.swift
3 | // SwiftRichString
4 | //
5 | // Created by Daniele Margutti on 05/05/2018.
6 | // Copyright © 2018 SwiftRichString. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import XCTest
11 | import SwiftRichString
12 |
13 | class SwiftRichStringTests: XCTestCase {
14 | func testExample() {
15 | // This is an example of a functional test case.
16 | // Use XCTAssert and related functions to verify your tests produce the correct results.
17 | //// XCTAssertEqual(SwiftRichString().text, "Hello, World!")
18 | }
19 |
20 | static var allTests = [
21 | ("testExample", testExample),
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/malcommac/SwiftRichString/e0b72d5c96968d7802856d2be096202c9798e8d1/banner.png
--------------------------------------------------------------------------------