├── .ruby-version
├── .xcodesamplecode.plist
├── demo.jpg
├── demo2.jpg
├── .gitignore
├── SwiftUISampleApp
├── AttributedTextSample
│ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── Preview Content
│ │ └── Preview Assets.xcassets
│ │ │ └── Contents.json
│ ├── AttributedTextSample.entitlements
│ ├── AppDelegate.swift
│ ├── ContentView.swift
│ ├── Base.lproj
│ │ └── LaunchScreen.storyboard
│ ├── Info.plist
│ ├── AttributedText.swift
│ └── SceneDelegate.swift
├── AttributedTextSampleTests
│ ├── Swift_logo_color_rgb.jpg
│ ├── ImageAttachmentTests.swift
│ └── Info.plist
└── AttributedTextSample.xcodeproj
│ ├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
│ ├── xcshareddata
│ └── xcschemes
│ │ └── AttributedTextSample.xcscheme
│ └── project.pbxproj
├── Tests
├── LinuxMain.swift
└── NSAttributedStringBuilderTests
│ ├── XCTestManifests.swift
│ ├── StaticComponentsTests.swift
│ ├── NSAttributedStringBuilderTests.swift
│ ├── ComponentBasicModifierTests.swift
│ └── ComponentParagraphSylteModifierTests.swift
├── .swiftpm
└── xcode
│ ├── package.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
│ └── xcshareddata
│ └── xcschemes
│ └── NSAttributedStringBuilder.xcscheme
├── .travis.yml
├── .github
└── workflows
│ └── swift.yml
├── Sources
└── NSAttributedStringBuilder
│ ├── Components
│ ├── AText.swift
│ ├── Link.swift
│ ├── StaticComponents.swift
│ ├── ImageAttachment.swift
│ └── Component.swift
│ └── NSAttributedStringBuilder.swift
├── CHANGELOG.md
├── LICENSE
├── Package.swift
├── NSAttributedStringBuilder13.podspec
└── README.md
/.ruby-version:
--------------------------------------------------------------------------------
1 | 2.7.0
2 |
--------------------------------------------------------------------------------
/.xcodesamplecode.plist:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ethanhuang13/NSAttributedStringBuilder/HEAD/demo.jpg
--------------------------------------------------------------------------------
/demo2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ethanhuang13/NSAttributedStringBuilder/HEAD/demo2.jpg
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /.build
3 | /Packages
4 | /*.xcodeproj
5 | xcuserdata
6 | *.xcuserstate
7 | *.xcscmblueprint
--------------------------------------------------------------------------------
/SwiftUISampleApp/AttributedTextSample/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/SwiftUISampleApp/AttributedTextSample/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/Tests/LinuxMain.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | import NSAttributedStringBuilderTests
4 |
5 | var tests = [XCTestCaseEntry]()
6 | tests += NSAttributedStringBuilderTests.allTests()
7 | XCTMain(tests)
8 |
--------------------------------------------------------------------------------
/SwiftUISampleApp/AttributedTextSampleTests/Swift_logo_color_rgb.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ethanhuang13/NSAttributedStringBuilder/HEAD/SwiftUISampleApp/AttributedTextSampleTests/Swift_logo_color_rgb.jpg
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/SwiftUISampleApp/AttributedTextSample.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Tests/NSAttributedStringBuilderTests/XCTestManifests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | #if !canImport(ObjectiveC)
4 | public func allTests() -> [XCTestCaseEntry] {
5 | [
6 | testCase(NSAttributedStringBuilderTests.allTests),
7 | ]
8 | }
9 | #endif
10 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: swift
2 | osx_image: xcode11
3 | script:
4 | - xcodebuild -sdk iphonesimulator -project ./SwiftUISampleApp/AttributedTextSample.xcodeproj -scheme AttributedTextSample -destination 'platform=iOS Simulator,name=iPhone 11 Pro Max,OS=13.0' test
5 | after_success:
6 | - bash <(curl -s https://codecov.io/bash)
7 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/package.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/SwiftUISampleApp/AttributedTextSample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.github/workflows/swift.yml:
--------------------------------------------------------------------------------
1 | name: Swift
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 | runs-on: macOS-latest
8 | env:
9 | DEVELOPER_DIR: /Applications/Xcode_12.5.app/Contents/Developer
10 | steps:
11 | - uses: actions/checkout@v1
12 | - name: Build
13 | run: swift build -v
14 | - name: Run tests
15 | run: swift test -v
16 |
--------------------------------------------------------------------------------
/SwiftUISampleApp/AttributedTextSample/AttributedTextSample.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.network.client
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/Sources/NSAttributedStringBuilder/Components/AText.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit)
2 | import UIKit
3 | #elseif canImport(AppKit)
4 | import AppKit
5 | #endif
6 |
7 | public typealias AText = NSAttributedString.AttrText
8 |
9 | public extension NSAttributedString {
10 | struct AttrText: Component {
11 | // MARK: Lifecycle
12 |
13 | public init(_ string: String, attributes: Attributes = [:]) {
14 | self.string = string
15 | self.attributes = attributes
16 | }
17 |
18 | // MARK: Public
19 |
20 | public let string: String
21 | public let attributes: Attributes
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/SwiftUISampleApp/AttributedTextSampleTests/ImageAttachmentTests.swift:
--------------------------------------------------------------------------------
1 | @testable import NSAttributedStringBuilder
2 | import XCTest
3 |
4 | final class ImageAttachmentTests: XCTestCase {
5 | func testSetImage_getAttachmentAttribute() {
6 | let testBundle = Bundle(for: ImageAttachmentTests.self)
7 | let testImage = UIImage(contentsOfFile: testBundle.path(forResource: "Swift_logo_color_rgb", ofType: "jpg")!)!
8 |
9 | let sut = NSAttributedString {
10 | ImageAttachment(testImage, size: CGSize(width: 40, height: 40))
11 | LineBreak()
12 | }
13 |
14 | XCTAssertNotNil(sut.attributes(at: 0, effectiveRange: nil)[.attachment])
15 | // TODO: Better test
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/SwiftUISampleApp/AttributedTextSampleTests/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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Sources/NSAttributedStringBuilder/Components/Link.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit)
2 | import UIKit
3 | #elseif canImport(AppKit)
4 | import AppKit
5 | #endif
6 |
7 | public typealias Link = NSAttributedString.Link
8 |
9 | public extension NSAttributedString {
10 | struct Link: Component {
11 | // MARK: Lifecycle
12 |
13 | public init(_ string: String, url: URL, attributes: Attributes = [:]) {
14 | self.string = string
15 | self.url = url
16 |
17 | var attributes = attributes
18 | attributes[.link] = url
19 | self.attributes = attributes
20 | }
21 |
22 | // MARK: Public
23 |
24 | public let string: String
25 | public let url: URL
26 | public let attributes: Attributes
27 |
28 | public var attributedString: NSAttributedString {
29 | NSAttributedString(string: string, attributes: attributes)
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Sources/NSAttributedStringBuilder/Components/StaticComponents.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public typealias Empty = NSAttributedString.Empty
4 | public typealias Space = NSAttributedString.Space
5 | public typealias LineBreak = NSAttributedString.LineBreak
6 |
7 | public extension NSAttributedString {
8 | struct Empty: Component {
9 | // MARK: Lifecycle
10 |
11 | public init() {}
12 |
13 | // MARK: Public
14 |
15 | public let string: String = ""
16 | public let attributes: Attributes = [:]
17 | }
18 |
19 | struct Space: Component {
20 | // MARK: Lifecycle
21 |
22 | public init() {}
23 |
24 | // MARK: Public
25 |
26 | public let string: String = " "
27 | public let attributes: Attributes = [:]
28 | }
29 |
30 | struct LineBreak: Component {
31 | // MARK: Lifecycle
32 |
33 | public init() {}
34 |
35 | // MARK: Public
36 |
37 | public let string: String = "\n"
38 | public let attributes: Attributes = [:]
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Sources/NSAttributedStringBuilder/NSAttributedStringBuilder.swift:
--------------------------------------------------------------------------------
1 | // NSAttributedString does not support SwiftUI Font and color, we still need to use UI/NS Font/Color
2 | #if canImport(UIKit)
3 | import UIKit
4 | public typealias Font = UIFont
5 | public typealias Color = UIColor
6 | #elseif canImport(AppKit)
7 | import AppKit
8 | public typealias Font = NSFont
9 | public typealias Color = NSColor
10 | #endif
11 |
12 | public typealias Attributes = [NSAttributedString.Key: Any]
13 |
14 | @resultBuilder
15 | public enum NSAttributedStringBuilder {
16 | public static func buildBlock(_ components: Component...) -> NSAttributedString {
17 | let mas = NSMutableAttributedString(string: "")
18 | for component in components {
19 | mas.append(component.attributedString)
20 | }
21 | return mas
22 | }
23 | }
24 |
25 | public extension NSAttributedString {
26 | convenience init(@NSAttributedStringBuilder _ builder: () -> NSAttributedString) {
27 | self.init(attributedString: builder())
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # NSAttributedStringBuilder CHANGELOG
2 |
3 | All notable changes to this project will be documented in this file.
4 | The format is based on [Keep a Changelog](http://keepachangelog.com/)
5 | and this project adheres to [Semantic Versioning](http://semver.org/).
6 |
7 | ## [Unreleased]
8 |
9 | ## [0.4.1] - 2021-04-28
10 | - [Fixed] Documents
11 |
12 | ## [0.4] - 2021-04-28
13 | - [Changed] Update to Swift 5.4, replaced `@_functionBuilder` with `@resultBuilder`
14 | - [Fixed] Typo of `ImageAttachment`
15 |
16 | ## [0.3.3] - 2019-11-19
17 | - [Added] CocoaPods support macOS and tvOS
18 |
19 | ## [0.3.2] - 2019-10-17
20 | - [Added] CocoaPods support
21 |
22 | ## [0.3.1] - 2019-07-20
23 | - [Removed] OS version requirements of Package.swift #1
24 |
25 | ## [0.3.0] - 2019-07-18
26 | - [Changed] Replace UIKitForMac to macCatalyst to reflect the changes in beta 4
27 | - [Changed] Replace .color() with .foregroundColor() to reflect the changes in beta 4
28 |
29 | ## [0.2.0] - 2019-07-12
30 | - [Added] Static components
31 | - [Changed] Rename AttrText's typealias to AText
32 |
33 | ## [0.1.0] - 2019-07-11
34 | - Initial version
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Ethan Huang
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 |
--------------------------------------------------------------------------------
/Sources/NSAttributedStringBuilder/Components/ImageAttachment.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit)
2 | import UIKit
3 |
4 | #if !os(watchOS)
5 | public typealias ImageAttachment = NSAttributedString.ImageAttachment
6 |
7 | public extension NSAttributedString {
8 | struct ImageAttachment: Component {
9 | // MARK: Lifecycle
10 |
11 | public init(_ image: UIImage, size: Size? = nil) {
12 | let attachment = NSTextAttachment()
13 | attachment.image = image
14 |
15 | if let size = size {
16 | attachment.bounds.size = size
17 | }
18 |
19 | self.attachment = attachment
20 | }
21 |
22 | // MARK: Public
23 |
24 | public let string: String = ""
25 | public let attributes: Attributes = [:]
26 |
27 | public var attributedString: NSAttributedString {
28 | NSAttributedString(attachment: attachment)
29 | }
30 |
31 | // MARK: Private
32 |
33 | private let attachment: NSTextAttachment
34 | }
35 | }
36 | #endif
37 |
38 | #endif
39 |
--------------------------------------------------------------------------------
/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: "NSAttributedStringBuilder",
8 | products: [
9 | // Products define the executables and libraries produced by a package, and make them visible to other packages.
10 | .library(
11 | name: "NSAttributedStringBuilder",
12 | targets: ["NSAttributedStringBuilder"]
13 | ),
14 | ],
15 | dependencies: [
16 | // Dependencies declare other packages that this package depends on.
17 | // .package(url: /* package url */, from: "1.0.0"),
18 | ],
19 | targets: [
20 | // Targets are the basic building blocks of a package. A target can define a module or a test suite.
21 | // Targets can depend on other targets in this package, and on products in packages which this package depends on.
22 | .target(
23 | name: "NSAttributedStringBuilder",
24 | dependencies: []
25 | ),
26 | .testTarget(
27 | name: "NSAttributedStringBuilderTests",
28 | dependencies: ["NSAttributedStringBuilder"]
29 | ),
30 | ]
31 | )
32 |
--------------------------------------------------------------------------------
/SwiftUISampleApp/AttributedTextSample/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | @UIApplicationMain
4 | class AppDelegate: UIResponder, UIApplicationDelegate {
5 | func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
6 | // Override point for customization after application launch.
7 | return true
8 | }
9 |
10 | // MARK: UISceneSession Lifecycle
11 |
12 | func application(_: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options _: UIScene.ConnectionOptions) -> UISceneConfiguration {
13 | // Called when a new scene session is being created.
14 | // Use this method to select a configuration to create the new scene with.
15 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
16 | }
17 |
18 | func application(_: UIApplication, didDiscardSceneSessions _: Set) {
19 | // Called when the user discards a scene session.
20 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
21 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Tests/NSAttributedStringBuilderTests/StaticComponentsTests.swift:
--------------------------------------------------------------------------------
1 | @testable import NSAttributedStringBuilder
2 | import XCTest
3 |
4 | final class StaticComponentsTests: XCTestCase {
5 | func testEmpty() {
6 | let testData: NSAttributedString = {
7 | let mas = NSMutableAttributedString(string: "")
8 | mas.append(NSAttributedString(string: ""))
9 | return mas
10 | }()
11 |
12 | let sut = NSAttributedString {
13 | Empty()
14 | Empty()
15 | }
16 |
17 | XCTAssertEqual(sut, testData)
18 | }
19 |
20 | func testSpace() {
21 | let testData: NSAttributedString = {
22 | let mas = NSMutableAttributedString(string: " ")
23 | return mas
24 | }()
25 |
26 | let sut = NSAttributedString {
27 | Empty()
28 | Space()
29 | }
30 |
31 | XCTAssertEqual(sut, testData)
32 | }
33 |
34 | func testLineBreak() {
35 | let testData: NSAttributedString = {
36 | let mas = NSMutableAttributedString(string: "")
37 | mas.append(NSAttributedString(string: "\n"))
38 | mas.append(NSAttributedString(string: ""))
39 | return mas
40 | }()
41 |
42 | let sut = NSAttributedString {
43 | Empty()
44 | LineBreak()
45 | Empty()
46 | }
47 |
48 | XCTAssertEqual(sut, testData)
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/SwiftUISampleApp/AttributedTextSample/ContentView.swift:
--------------------------------------------------------------------------------
1 | import NSAttributedStringBuilder
2 | import SwiftUI
3 |
4 | let image = UIImage(named: "Swift_logo_color_rgb.jpg")!
5 |
6 | struct ContentView: View {
7 | var body: some View {
8 | VStack(alignment: .leading) {
9 | Text("Text Title")
10 | .font(.largeTitle)
11 | Text("Text Subtitle")
12 | .font(.headline)
13 | Text("Text Link")
14 | .font(.body)
15 | .underline()
16 | .foregroundColor(.blue)
17 | Image(uiImage: image)
18 | .padding(.bottom)
19 |
20 | // UITextView: UIViewRepresentable
21 | AttributedText {
22 | AText("AttributedText Title")
23 | .font(.preferredFont(forTextStyle: .largeTitle))
24 | LineBreak()
25 | AText("AttributedText Subtitle")
26 | .font(.preferredFont(forTextStyle: .headline))
27 | LineBreak()
28 | Link("Attributed Link", url: URL(string: "https://www.apple.com")!)
29 | .font(.preferredFont(forTextStyle: .body))
30 | LineBreak()
31 | ImageAttachment(image)
32 | }
33 | }
34 | }
35 | }
36 |
37 | #if DEBUG
38 | struct ContentView_Previews: PreviewProvider {
39 | static var previews: some View {
40 | ContentView()
41 | }
42 | }
43 | #endif
44 |
--------------------------------------------------------------------------------
/Tests/NSAttributedStringBuilderTests/NSAttributedStringBuilderTests.swift:
--------------------------------------------------------------------------------
1 | @testable import NSAttributedStringBuilder
2 | import XCTest
3 |
4 | final class NSAttributedStringBuilderTests: XCTestCase {
5 | static var allTests = [
6 | ("testInitWithTextAndLink", testInitWithTextAndLink),
7 | ]
8 |
9 | func testInitWithTwoAText() {
10 | let testData: NSAttributedString = {
11 | let mas = NSMutableAttributedString(string: "Hello world")
12 | mas.append(NSAttributedString(string: " with Swift"))
13 | return mas
14 | }()
15 |
16 | let sut = NSAttributedString {
17 | AText("Hello world")
18 | AText(" with Swift")
19 | }
20 |
21 | XCTAssertEqual(sut, testData)
22 | }
23 |
24 | func testInitWithTextAndLink() {
25 | let testData: NSAttributedString = {
26 | let mas = NSMutableAttributedString(string: "")
27 | mas.append(NSAttributedString(string: "Here is a link to ",
28 | attributes: [.foregroundColor: Color.brown]))
29 | mas.append(NSAttributedString(string: "Apple",
30 | attributes: [.link: URL(string: "https://www.apple.com")!]))
31 | return mas
32 | }()
33 |
34 | let sut = NSAttributedString {
35 | AText("Here is a link to ")
36 | .foregroundColor(.brown)
37 | Link("Apple", url: URL(string: "https://www.apple.com")!)
38 | }
39 |
40 | XCTAssertEqual(sut, testData)
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/SwiftUISampleApp/AttributedTextSample/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 |
--------------------------------------------------------------------------------
/NSAttributedStringBuilder13.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # Be sure to run `pod lib lint NSAttributedStringBuilder.podspec' to ensure this is a
3 | # valid spec before submitting.
4 | #
5 | # Any lines starting with a # are optional, but their use is encouraged
6 | # To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html
7 | #
8 |
9 | Pod::Spec.new do |s|
10 | s.name = 'NSAttributedStringBuilder13'
11 | s.version = '0.4.1'
12 | s.summary = 'Composing NSAttributedString with SwiftUI-style syntax, powered by Result Builder.'
13 |
14 | # This description is used to generate tags and improve search results.
15 | # * Think: What does it do? Why did you write it? What is the focus?
16 | # * Try to keep it short, snappy and to the point.
17 | # * Write the description between the DESC delimiters below.
18 | # * Finally, don't worry about the indent, CocoaPods strips it!
19 |
20 | s.description = <<-DESC
21 | Composing NSAttributedString with SwiftUI-style syntax, powered by Result Builder.
22 |
23 | Project Link: https://github.com/ethanhuang13/NSAttributedStringBuilder
24 | DESC
25 |
26 | s.homepage = 'https://github.com/ethanhuang13/NSAttributedStringBuilder'
27 | s.swift_versions = '5.4'
28 | # s.screenshots = 'https://github.com/ethanhuang13/NSAttributedStringBuilder/blob/master/demo2.jpg',
29 | 'https://github.com/ethanhuang13/NSAttributedStringBuilder/blob/master/demo.jpg'
30 | s.license = { :type => 'MIT', :file => 'LICENSE' }
31 | s.author = { 'ethanhuang13' => 'blesserx@gmail.com' }
32 | s.source = { :git => 'https://github.com/ethanhuang13/NSAttributedStringBuilder.git', :tag => s.version.to_s }
33 | s.social_media_url = 'https://twitter.com/ethanhuang13'
34 |
35 | s.ios.deployment_target = '8.0'
36 | s.osx.deployment_target = '10.10'
37 | s.tvos.deployment_target = '9.0'
38 |
39 | s.source_files = 'Sources/NSAttributedStringBuilder/*',
40 | 'Sources/NSAttributedStringBuilder/Components/*'
41 | end
42 |
--------------------------------------------------------------------------------
/SwiftUISampleApp/AttributedTextSample/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 | }
--------------------------------------------------------------------------------
/SwiftUISampleApp/AttributedTextSample/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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIApplicationSceneManifest
24 |
25 | UIApplicationSupportsMultipleScenes
26 |
27 | UISceneConfigurations
28 |
29 | UIWindowSceneSessionRoleApplication
30 |
31 |
32 | UILaunchStoryboardName
33 | LaunchScreen
34 | UISceneConfigurationName
35 | Default Configuration
36 | UISceneDelegateClassName
37 | $(PRODUCT_MODULE_NAME).SceneDelegate
38 |
39 |
40 |
41 |
42 | UILaunchStoryboardName
43 | LaunchScreen
44 | UIRequiredDeviceCapabilities
45 |
46 | armv7
47 |
48 | UISupportedInterfaceOrientations
49 |
50 | UIInterfaceOrientationPortrait
51 | UIInterfaceOrientationLandscapeLeft
52 | UIInterfaceOrientationLandscapeRight
53 |
54 | UISupportedInterfaceOrientations~ipad
55 |
56 | UIInterfaceOrientationPortrait
57 | UIInterfaceOrientationPortraitUpsideDown
58 | UIInterfaceOrientationLandscapeLeft
59 | UIInterfaceOrientationLandscapeRight
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/SwiftUISampleApp/AttributedTextSample/AttributedText.swift:
--------------------------------------------------------------------------------
1 | import NSAttributedStringBuilder
2 | import SwiftUI
3 |
4 | /// A custom view to use NSAttributedString in SwiftUI
5 | public final class AttributedText: UIViewRepresentable {
6 | // MARK: Lifecycle
7 |
8 | private init(_ attributedString: NSAttributedString) {
9 | self.attributedString = attributedString
10 | }
11 |
12 | public convenience init(@NSAttributedStringBuilder _ builder: () -> NSAttributedString) {
13 | self.init(builder())
14 | }
15 |
16 | // MARK: Public
17 |
18 | public func makeUIView(context _: UIViewRepresentableContext) -> UITextView {
19 | let textView = UITextView(frame: .zero)
20 | textView.attributedText = attributedString
21 | textView.isEditable = false
22 | textView.backgroundColor = .clear
23 | textView.textAlignment = .center
24 | return textView
25 | }
26 |
27 | public func updateUIView(_ textView: UITextView, context _: UIViewRepresentableContext) {
28 | textView.attributedText = attributedString
29 | }
30 |
31 | // MARK: Internal
32 |
33 | var attributedString: NSAttributedString
34 | }
35 |
36 | #if DEBUG
37 | struct AttributedText_Previews: PreviewProvider {
38 | static var previews: some View {
39 | AttributedText {
40 | ImageAttachment(UIImage(named: "Swift_logo_color_rgb.jpg")!, size: CGSize(width: 90, height: 90))
41 | LineBreak()
42 | .lineSpacing(20)
43 | AText("Hello SwiftUI")
44 | .backgroundColor(.red)
45 | .baselineOffset(10)
46 | .font(.systemFont(ofSize: 20))
47 | .foregroundColor(.yellow)
48 | .expansion(1)
49 | .kerning(3)
50 | .ligature(.none)
51 | .obliqueness(0.5)
52 | .shadow(color: .black, radius: 10, x: 4, y: 4)
53 | .strikethrough(style: .patternDash, color: .black)
54 | .stroke(width: -2, color: .green)
55 | .underline(.patternDashDotDot, color: .cyan)
56 | LineBreak()
57 | AText(" with fun")
58 | .paragraphSpacing(10, before: 60)
59 | .alignment(.right)
60 | }
61 | }
62 | }
63 | #endif
64 |
--------------------------------------------------------------------------------
/SwiftUISampleApp/AttributedTextSample/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | import SwiftUI
2 | import UIKit
3 |
4 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
5 | var window: UIWindow?
6 |
7 | func scene(_ scene: UIScene, willConnectTo _: UISceneSession, options _: UIScene.ConnectionOptions) {
8 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
9 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
10 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
11 |
12 | // Use a UIHostingController as window root view controller
13 | if let windowScene = scene as? UIWindowScene {
14 | let window = UIWindow(windowScene: windowScene)
15 | window.rootViewController = UIHostingController(rootView: ContentView())
16 | self.window = window
17 | window.makeKeyAndVisible()
18 | }
19 | }
20 |
21 | func sceneDidDisconnect(_: UIScene) {
22 | // Called as the scene is being released by the system.
23 | // This occurs shortly after the scene enters the background, or when its session is discarded.
24 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
25 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
26 | }
27 |
28 | func sceneDidBecomeActive(_: UIScene) {
29 | // Called when the scene has moved from an inactive state to an active state.
30 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
31 | }
32 |
33 | func sceneWillResignActive(_: UIScene) {
34 | // Called when the scene will move from an active state to an inactive state.
35 | // This may occur due to temporary interruptions (ex. an incoming phone call).
36 | }
37 |
38 | func sceneWillEnterForeground(_: UIScene) {
39 | // Called as the scene transitions from the background to the foreground.
40 | // Use this method to undo the changes made on entering the background.
41 | }
42 |
43 | func sceneDidEnterBackground(_: UIScene) {
44 | // Called as the scene transitions from the foreground to the background.
45 | // Use this method to save data, release shared resources, and store enough scene-specific state information
46 | // to restore the scene back to its current state.
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NSAttributedStringBuilder
2 | [](https://github.com/ethanhuang13/NSAttributedStringBuilder/actions?workflow=Swift)
3 | [](https://codecov.io/gh/ethanhuang13/NSAttributedStringBuilder)
4 | []()
5 | 
6 | [](https://github.com/ethanhuang13/ladybug/blob/master/LICENSE)
7 | [](https://twitter.com/ethanhuang13)
8 | [](https://paypal.me/ethanhuang13)
9 |
10 | Composing `NSAttributedString` with SwiftUI-style syntax, powered by [Result Builder](https://forums.swift.org/t/function-builders/25167).
11 |
12 | Project Link: [https://github.com/ethanhuang13/NSAttributedStringBuilder](https://github.com/ethanhuang13/NSAttributedStringBuilder)
13 |
14 | ## Features
15 |
16 | | | Features |
17 | | --- | --- |
18 | | 🐦 | Open source library written in Swift 5.4 |
19 | | 🍬 | SwiftUI-like syntax |
20 | | 💪 | Support most attributes in `NSAttributedString.Key` |
21 | | 📦 | Distribution with Swift Package Manager |
22 | | 🧪 | Fully tested code |
23 | | 🛠 | Continuously integrates in [Swift Source Compatibility Suite](https://github.com/apple/swift-source-compat-suite/pull/373) |
24 |
25 | ## How to use?
26 |
27 | Traditionally we compose a `NSAttributedString` like this:
28 |
29 | ```Swift
30 | let mas = NSMutableAttributedString(string: "")
31 | mas.append(NSAttributedString(string: "Hello world", attributes: [.font: UIFont.systemFont(ofSize: 24), .foregroundColor: UIColor.red]))
32 | mas.append(NSAttributedString(string: "\n"))
33 | mas.append(NSAttributedString(string: "with Swift", attributes: [.font: UIFont.systemFont(ofSize: 20), .foregroundColor: UIColor.orange]))
34 |
35 | ```
36 | Now, with **NSAttributedStringBuilder**, we can use SwiftUI-like syntax to declare `NSAttributedString`:
37 |
38 | ```Swift
39 | let attributedString = NSAttributedString {
40 | AText("Hello world")
41 | .font(.systemFont(ofSize: 24))
42 | .foregroundColor(.red)
43 | LineBreak()
44 | AText("with Swift")
45 | .font(.systemFont(ofSize: 20))
46 | .foregroundColor(.orange)
47 | }
48 |
49 | ```
50 |
51 | ## Requirements
52 | Xcode 12.5. This project uses Swift 5.4 feature [Result Builder](https://github.com/apple/swift-evolution/blob/main/proposals/0289-result-builders.md).
53 |
54 | ## Installation
55 |
56 | ### Swift Package
57 | Open your project in Xcode 12, navigate to **Menu -> Swift Packages -> Add Package Dependency** and enter [https://github.com/ethanhuang13/NSAttributedStringBuilder](https://github.com/ethanhuang13/NSAttributedStringBuilder) to install.
58 |
59 | ### CocoaPods
60 | Add `pod 'NSAttributedStringBuilder13'` to your `Podfile`.
61 |
62 | ## SwiftUI Sample Project
63 | Besides clearer `NSAttributedString` syntax, since **NSAttributedStringBuilder** uses Result Builder it also enables API to build components in `UIViewRepresentable`(which embeds `UIView` in a SwiftUI `View`).
64 |
65 | Just like a SwiftUI `Text` takes a `String` as input, the purpose of `AttributedText` in the sample project is to take a `NSAttributedString` as input and render in SwiftUI.
66 |
67 | To achieve this, `AttributedText.swift` uses `@NSAttributedStringBuilder` to support SwiftUI-style syntax:
68 |
69 | 
70 |
71 | Then using an `AttributedText` will be like:
72 | 
73 |
74 | Open the sample in ***/SwiftUISampleApp/AttributedTextSample.xcodeproj*** and check `AttributedText`. It uses `UITextView`, you can also use `UILabel` or `NSTextView`.
75 |
76 | ## TODO
77 | * Better tests for image attachment
78 |
79 | ## Known Issue
80 | * `NSAttributedString` does not support link color, therefore `Link` component with a `.color()` modifier has no effect. Alternatively you need to specify in `UITextView.linkTextAttributes` or `.tintColor`.
81 |
82 | ## Others
83 | Initially discussed on this [Twitter thread](https://twitter.com/ethanhuang13/status/1148135534826442752). Some code are inspired by [zonble](https://github.com/zonble/NSAttributedStringBuilder)🙏.
84 |
--------------------------------------------------------------------------------
/.swiftpm/xcode/xcshareddata/xcschemes/NSAttributedStringBuilder.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
38 |
39 |
45 |
46 |
52 |
53 |
54 |
55 |
57 |
63 |
64 |
65 |
67 |
68 |
69 |
70 |
71 |
72 |
82 |
83 |
89 |
90 |
96 |
97 |
98 |
99 |
101 |
102 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/SwiftUISampleApp/AttributedTextSample.xcodeproj/xcshareddata/xcschemes/AttributedTextSample.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
32 |
33 |
39 |
40 |
46 |
47 |
48 |
49 |
51 |
57 |
58 |
59 |
61 |
67 |
68 |
69 |
70 |
71 |
81 |
83 |
89 |
90 |
91 |
92 |
98 |
100 |
106 |
107 |
108 |
109 |
111 |
112 |
115 |
116 |
117 |
--------------------------------------------------------------------------------
/Sources/NSAttributedStringBuilder/Components/Component.swift:
--------------------------------------------------------------------------------
1 | #if canImport(UIKit)
2 | import UIKit
3 | public typealias Size = CGSize
4 | #elseif canImport(AppKit)
5 | import AppKit
6 | public typealias Size = NSSize
7 | #endif
8 |
9 | public protocol Component {
10 | var string: String { get }
11 | var attributes: Attributes { get }
12 | var attributedString: NSAttributedString { get }
13 | }
14 |
15 | public enum Ligature: Int {
16 | case none = 0
17 | case `default` = 1
18 |
19 | #if canImport(AppKit)
20 | case all = 2 // Value 2 is unsupported on iOS
21 | #endif
22 | }
23 |
24 | public extension Component {
25 | private func build(_ string: String, attributes: Attributes) -> Component {
26 | AText(string, attributes: attributes)
27 | }
28 |
29 | var attributedString: NSAttributedString {
30 | NSAttributedString(string: string, attributes: attributes)
31 | }
32 |
33 | func attribute(_ newAttribute: NSAttributedString.Key, value: Any) -> Component {
34 | attributes([newAttribute: value])
35 | }
36 |
37 | func attributes(_ newAttributes: Attributes) -> Component {
38 | var attributes = self.attributes
39 | for attribute in newAttributes {
40 | attributes[attribute.key] = attribute.value
41 | }
42 | return build(string, attributes: attributes)
43 | }
44 | }
45 |
46 | // MARK: Basic Modifiers
47 |
48 | public extension Component {
49 | func backgroundColor(_ color: Color) -> Component {
50 | attributes([.backgroundColor: color])
51 | }
52 |
53 | func baselineOffset(_ baselineOffset: CGFloat) -> Component {
54 | attributes([.baselineOffset: baselineOffset])
55 | }
56 |
57 | func font(_ font: Font) -> Component {
58 | attributes([.font: font])
59 | }
60 |
61 | func foregroundColor(_ color: Color) -> Component {
62 | attributes([.foregroundColor: color])
63 | }
64 |
65 | func expansion(_ expansion: CGFloat) -> Component {
66 | attributes([.expansion: expansion])
67 | }
68 |
69 | func kerning(_ kern: CGFloat) -> Component {
70 | attributes([.kern: kern])
71 | }
72 |
73 | func ligature(_ ligature: Ligature) -> Component {
74 | attributes([.ligature: ligature.rawValue])
75 | }
76 |
77 | func obliqueness(_ obliqueness: Float) -> Component {
78 | attributes([.obliqueness: obliqueness])
79 | }
80 |
81 | func shadow(color: Color? = nil, radius: CGFloat, x: CGFloat, y: CGFloat) -> Component {
82 | let shadow = NSShadow()
83 | shadow.shadowColor = color
84 | shadow.shadowBlurRadius = radius
85 | shadow.shadowOffset = .init(width: x, height: y)
86 | return attributes([.shadow: shadow])
87 | }
88 |
89 | func strikethrough(style: NSUnderlineStyle, color: Color? = nil) -> Component {
90 | if let color = color {
91 | return attributes([.strikethroughStyle: style.rawValue,
92 | .strikethroughColor: color])
93 | } else {
94 | return attributes([.strikethroughStyle: style.rawValue])
95 | }
96 | }
97 |
98 | func stroke(width: CGFloat, color: Color? = nil) -> Component {
99 | if let color = color {
100 | return attributes([.strokeWidth: width,
101 | .strokeColor: color])
102 | } else {
103 | return attributes([.strokeWidth: width])
104 | }
105 | }
106 |
107 | func textEffect(_ textEffect: NSAttributedString.TextEffectStyle) -> Component {
108 | attributes([.textEffect: textEffect])
109 | }
110 |
111 | func underline(_ style: NSUnderlineStyle, color: Color? = nil) -> Component {
112 | if let color = color {
113 | return attributes([.underlineStyle: style.rawValue,
114 | .underlineColor: color])
115 | } else {
116 | return attributes([.underlineStyle: style.rawValue])
117 | }
118 | }
119 |
120 | func writingDirection(_ writingDirection: NSWritingDirection) -> Component {
121 | attributes([.writingDirection: writingDirection.rawValue])
122 | }
123 |
124 | #if canImport(AppKit)
125 | func vertical() -> Component {
126 | attributes([.verticalGlyphForm: 1])
127 | }
128 | #endif
129 | }
130 |
131 | // MARK: - Paragraph Style Modifiers
132 |
133 | public extension Component {
134 | func paragraphStyle(_ paragraphStyle: NSParagraphStyle) -> Component {
135 | attributes([.paragraphStyle: paragraphStyle])
136 | }
137 |
138 | func paragraphStyle(_ paragraphStyle: NSMutableParagraphStyle) -> Component {
139 | attributes([.paragraphStyle: paragraphStyle])
140 | }
141 |
142 | private func getMutableParagraphStyle() -> NSMutableParagraphStyle {
143 | if let mps = attributes[.paragraphStyle] as? NSMutableParagraphStyle {
144 | return mps
145 | } else if let ps = attributes[.paragraphStyle] as? NSParagraphStyle,
146 | let mps = ps.mutableCopy() as? NSMutableParagraphStyle {
147 | return mps
148 | } else {
149 | return NSMutableParagraphStyle()
150 | }
151 | }
152 |
153 | func alignment(_ alignment: NSTextAlignment) -> Component {
154 | let paragraphStyle = getMutableParagraphStyle()
155 | paragraphStyle.alignment = alignment
156 | return self.paragraphStyle(paragraphStyle)
157 | }
158 |
159 | func firstLineHeadIndent(_ indent: CGFloat) -> Component {
160 | let paragraphStyle = getMutableParagraphStyle()
161 | paragraphStyle.firstLineHeadIndent = indent
162 | return self.paragraphStyle(paragraphStyle)
163 | }
164 |
165 | func headIndent(_ indent: CGFloat) -> Component {
166 | let paragraphStyle = getMutableParagraphStyle()
167 | paragraphStyle.headIndent = indent
168 | return self.paragraphStyle(paragraphStyle)
169 | }
170 |
171 | func tailIndent(_ indent: CGFloat) -> Component {
172 | let paragraphStyle = getMutableParagraphStyle()
173 | paragraphStyle.tailIndent = indent
174 | return self.paragraphStyle(paragraphStyle)
175 | }
176 |
177 | func lineBreakeMode(_ lineBreakMode: NSLineBreakMode) -> Component {
178 | let paragraphStyle = getMutableParagraphStyle()
179 | paragraphStyle.lineBreakMode = lineBreakMode
180 | return self.paragraphStyle(paragraphStyle)
181 | }
182 |
183 | func lineHeight(multiple: CGFloat = 0, maximum: CGFloat = 0, minimum: CGFloat) -> Component {
184 | let paragraphStyle = getMutableParagraphStyle()
185 | paragraphStyle.lineHeightMultiple = multiple
186 | paragraphStyle.maximumLineHeight = maximum
187 | paragraphStyle.minimumLineHeight = minimum
188 | return self.paragraphStyle(paragraphStyle)
189 | }
190 |
191 | func lineSpacing(_ spacing: CGFloat) -> Component {
192 | let paragraphStyle = getMutableParagraphStyle()
193 | paragraphStyle.lineSpacing = spacing
194 | return self.paragraphStyle(paragraphStyle)
195 | }
196 |
197 | func paragraphSpacing(_ spacing: CGFloat, before: CGFloat = 0) -> Component {
198 | let paragraphStyle = getMutableParagraphStyle()
199 | paragraphStyle.paragraphSpacing = spacing
200 | paragraphStyle.paragraphSpacingBefore = before
201 | return self.paragraphStyle(paragraphStyle)
202 | }
203 |
204 | func baseWritingDirection(_ baseWritingDirection: NSWritingDirection) -> Component {
205 | let paragraphStyle = getMutableParagraphStyle()
206 | paragraphStyle.baseWritingDirection = baseWritingDirection
207 | return self.paragraphStyle(paragraphStyle)
208 | }
209 |
210 | func hyphenationFactor(_ hyphenationFactor: Float) -> Component {
211 | let paragraphStyle = getMutableParagraphStyle()
212 | paragraphStyle.hyphenationFactor = hyphenationFactor
213 | return self.paragraphStyle(paragraphStyle)
214 | }
215 |
216 | @available(iOS 9.0, tvOS 9.0, watchOS 2.0, OSX 10.11, *)
217 | func allowsDefaultTighteningForTruncation() -> Component {
218 | let paragraphStyle = getMutableParagraphStyle()
219 | paragraphStyle.allowsDefaultTighteningForTruncation = true
220 | return self.paragraphStyle(paragraphStyle)
221 | }
222 |
223 | func tabsStops(_ tabStops: [NSTextTab], defaultInterval: CGFloat = 0) -> Component {
224 | let paragraphStyle = getMutableParagraphStyle()
225 | paragraphStyle.tabStops = tabStops
226 | paragraphStyle.defaultTabInterval = defaultInterval
227 | return self.paragraphStyle(paragraphStyle)
228 | }
229 |
230 | #if canImport(AppKit) && !targetEnvironment(macCatalyst)
231 | func textBlocks(_ textBlocks: [NSTextBlock]) -> Component {
232 | let paragraphStyle = getMutableParagraphStyle()
233 | paragraphStyle.textBlocks = textBlocks
234 | return self.paragraphStyle(paragraphStyle)
235 | }
236 |
237 | func textLists(_ textLists: [NSTextList]) -> Component {
238 | let paragraphStyle = getMutableParagraphStyle()
239 | paragraphStyle.textLists = textLists
240 | return self.paragraphStyle(paragraphStyle)
241 | }
242 |
243 | func tighteningFactorForTruncation(_ tighteningFactorForTruncation: Float) -> Component {
244 | let paragraphStyle = getMutableParagraphStyle()
245 | paragraphStyle.tighteningFactorForTruncation = tighteningFactorForTruncation
246 | return self.paragraphStyle(paragraphStyle)
247 | }
248 |
249 | func headerLevel(_ headerLevel: Int) -> Component {
250 | let paragraphStyle = getMutableParagraphStyle()
251 | paragraphStyle.headerLevel = headerLevel
252 | return self.paragraphStyle(paragraphStyle)
253 | }
254 | #endif
255 | }
256 |
257 | // MARK: - No 'modifiers' for these keys. Use .attributes() or .attribute(:value:) instead.
258 |
259 | /*
260 | #if canImport(UIKit)
261 | let iOSKeys: [NSAttributedString.Key] = [
262 | .UIAccessibilitySpeechAttributeSpellOut, // iOS 13+
263 | .UIAccessibilityTextAttributeContext, // iOS 13+
264 | .accessibilitySpeechIPANotation, // iOS 11.0+
265 | .accessibilitySpeechLanguage, // iOS 7.0+
266 | .accessibilitySpeechPitch, // iOS 7.0+
267 | .accessibilitySpeechPunctuation, // iOS 7.0+
268 | .accessibilitySpeechQueueAnnouncement, // iOS 11.0+
269 | .accessibilityTextCustom, // iOS 11.0+
270 | .accessibilityTextHeadingLevel, // iOS 11.0+
271 | ]
272 | #endif
273 |
274 | #if canImport(AppKit) && !targetEnvironment(UIKitForMac)
275 | let macOSKeys: [NSAttributedString.Key] = [
276 | .accessibilityAlignment, // macOS 10.12+
277 | .accessibilityAnnotationTextAttribute, // macOS 10.13+
278 | .accessibilityAttachment, // macOS 10.4+, Deprecated
279 | .accessibilityAutocorrected, // macOS 10.7+
280 | .accessibilityBackgroundColor, // macOS 10.4+
281 | .accessibilityCustomText, // macOS 10.13+
282 | .accessibilityFont, // macOS 10.4+
283 | .accessibilityForegroundColor, // macOS 10.4+
284 | .accessibilityLanguage, // macOS 10.13+
285 | .accessibilityLink, // macOS 10.4+
286 | .accessibilityListItemIndex, // macOS 10.11+
287 | .accessibilityListItemLevel, // macOS 10.11+
288 | .accessibilityListItemPrefix, // macOS 10.11+
289 | .accessibilityMarkedMisspelled, // macOS 10.4+
290 | .accessibilityMisspelled, // macOS 10.4+
291 | .accessibilityShadow, // macOS 10.4+
292 | .accessibilityStrikethrough, // macOS 10.4+
293 | .accessibilityStrikethroughColor, // macOS 10.4+
294 | .accessibilitySuperscript, // macOS 10.4+
295 | .accessibilityUnderline, // macOS 10.4+
296 | .accessibilityUnderlineColor, // macOS 10.4+
297 | .cursor, // macOS 10.3+
298 | .glyphInfo, // macOS 10.2+
299 | .markedClauseSegment, // macOS 10.5+
300 | .spellingState, // macOS 10.5+
301 | .superscript, // macOS 10.0+
302 | .textAlternatives, // macOS 10.8+
303 | .toolTip, // macOS 10.3+
304 | ]
305 | #endif
306 | */
307 |
--------------------------------------------------------------------------------
/Tests/NSAttributedStringBuilderTests/ComponentBasicModifierTests.swift:
--------------------------------------------------------------------------------
1 | @testable import NSAttributedStringBuilder
2 | import XCTest
3 |
4 | final class ComponentBasicModifierTests: XCTestCase {
5 | func testModifyWithSingleAttribute() {
6 | let testData: NSAttributedString = {
7 | let mas = NSMutableAttributedString(string: "Hello world",
8 | attributes: [.foregroundColor: Color.yellow])
9 | mas.append(NSAttributedString(string: " with Swift"))
10 | return mas
11 | }()
12 |
13 | let sut = NSAttributedString {
14 | AText("Hello world")
15 | .attribute(.foregroundColor, value: Color.yellow)
16 | AText(" with Swift")
17 | }
18 |
19 | XCTAssertEqual(sut, testData)
20 | }
21 |
22 | func testModifyBackgroundColor() {
23 | let testData: NSAttributedString = {
24 | let mas = NSMutableAttributedString(string: "Hello world",
25 | attributes: [.backgroundColor: Color.red])
26 | mas.append(NSAttributedString(string: " with Swift"))
27 | return mas
28 | }()
29 |
30 | let sut = NSAttributedString {
31 | AText("Hello world")
32 | .backgroundColor(.red)
33 | AText(" with Swift")
34 | }
35 |
36 | XCTAssertEqual(sut, testData)
37 | }
38 |
39 | func testModifyBaselineOffset() {
40 | let testData: NSAttributedString = {
41 | let mas = NSMutableAttributedString(string: "Hello world",
42 | attributes: [.baselineOffset: 10])
43 | mas.append(NSAttributedString(string: " with Swift"))
44 | return mas
45 | }()
46 |
47 | let sut = NSAttributedString {
48 | AText("Hello world")
49 | .baselineOffset(10)
50 | AText(" with Swift")
51 | }
52 |
53 | XCTAssertEqual(sut, testData)
54 | }
55 |
56 | func testModifyFontAndColor() {
57 | let testData: NSAttributedString = {
58 | let mas = NSMutableAttributedString(string: "")
59 | mas.append(NSAttributedString(string: "Hello world",
60 | attributes: [
61 | .font: Font.systemFont(ofSize: 20),
62 | .foregroundColor: Color.yellow,
63 | ]))
64 | mas.append(NSAttributedString(string: "\n"))
65 | mas.append(NSAttributedString(string: "Second line",
66 | attributes: [.font: Font.systemFont(ofSize: 24)]))
67 | return mas
68 | }()
69 |
70 | let sut = NSAttributedString {
71 | AText("Hello world")
72 | .font(.systemFont(ofSize: 20))
73 | .foregroundColor(.yellow)
74 | LineBreak()
75 | AText("Second line")
76 | .font(.systemFont(ofSize: 24))
77 | }
78 |
79 | XCTAssertEqual(sut, testData)
80 | }
81 |
82 | func testModifyExpansion() {
83 | let testData: NSAttributedString = {
84 | let mas = NSMutableAttributedString(string: "Hello world",
85 | attributes: [.expansion: 1])
86 | mas.append(NSAttributedString(string: " with Swift"))
87 | return mas
88 | }()
89 |
90 | let sut = NSAttributedString {
91 | AText("Hello world")
92 | .expansion(1)
93 | AText(" with Swift")
94 | }
95 |
96 | XCTAssertEqual(sut, testData)
97 | }
98 |
99 | func testModifyKerning() {
100 | let testData: NSAttributedString = {
101 | let mas = NSMutableAttributedString(string: "Hello world",
102 | attributes: [.kern: 3])
103 | mas.append(NSAttributedString(string: " with Swift"))
104 | return mas
105 | }()
106 |
107 | let sut = NSAttributedString {
108 | AText("Hello world")
109 | .kerning(3)
110 | AText(" with Swift")
111 | }
112 |
113 | XCTAssertEqual(sut, testData)
114 | }
115 |
116 | func testModifyLigature() {
117 | let testData: NSAttributedString = {
118 | let mas = NSMutableAttributedString(string: "Hello world",
119 | attributes: [.ligature: 0])
120 | mas.append(NSAttributedString(string: " with Swift"))
121 | return mas
122 | }()
123 |
124 | let sut = NSAttributedString {
125 | AText("Hello world")
126 | .ligature(.none)
127 | AText(" with Swift")
128 | }
129 |
130 | XCTAssertEqual(sut, testData)
131 | }
132 |
133 | func testModifyObliqueness() {
134 | let testData: NSAttributedString = {
135 | let mas = NSMutableAttributedString(string: "Hello world",
136 | attributes: [.obliqueness: 0.5])
137 | mas.append(NSAttributedString(string: " with Swift"))
138 | return mas
139 | }()
140 |
141 | let sut = NSAttributedString {
142 | AText("Hello world")
143 | .obliqueness(0.5)
144 | AText(" with Swift")
145 | }
146 |
147 | XCTAssertEqual(sut, testData)
148 | }
149 |
150 | func testModifyShadow() {
151 | let testData: NSAttributedString = {
152 | let shadow = NSShadow()
153 | shadow.shadowColor = Color.black
154 | shadow.shadowBlurRadius = 10
155 | shadow.shadowOffset = .init(width: 4, height: 4)
156 |
157 | let mas = NSMutableAttributedString(string: "Hello world",
158 | attributes: [.shadow: shadow])
159 | mas.append(NSAttributedString(string: " with Swift"))
160 | return mas
161 | }()
162 |
163 | let sut = NSAttributedString {
164 | AText("Hello world")
165 | .shadow(color: .black, radius: 10, x: 4, y: 4)
166 | AText(" with Swift")
167 | }
168 |
169 | XCTAssertEqual(sut, testData)
170 | }
171 |
172 | func testModifyStrikethrough() {
173 | let testData: NSAttributedString = {
174 | let mas = NSMutableAttributedString(string: "Hello world",
175 | attributes: [.strikethroughStyle: NSUnderlineStyle.double.rawValue])
176 | mas.append(NSAttributedString(string: " with Swift"))
177 | return mas
178 | }()
179 |
180 | let sut = NSAttributedString {
181 | AText("Hello world")
182 | .strikethrough(style: .double)
183 | AText(" with Swift")
184 | }
185 |
186 | XCTAssertEqual(sut, testData)
187 | }
188 |
189 | func testModifyStrikethroughWithColor() {
190 | let testData: NSAttributedString = {
191 | let mas = NSMutableAttributedString(string: "Hello world",
192 | attributes: [.strikethroughStyle: NSUnderlineStyle.patternDash.rawValue,
193 | .strikethroughColor: Color.black])
194 | mas.append(NSAttributedString(string: " with Swift"))
195 | return mas
196 | }()
197 |
198 | let sut = NSAttributedString {
199 | AText("Hello world")
200 | .strikethrough(style: .patternDash, color: .black)
201 | AText(" with Swift")
202 | }
203 |
204 | XCTAssertEqual(sut, testData)
205 | }
206 |
207 | func testModifyStroke() {
208 | let testData: NSAttributedString = {
209 | let mas = NSMutableAttributedString(string: "Hello world",
210 | attributes: [.strokeWidth: -2])
211 | mas.append(NSAttributedString(string: " with Swift"))
212 | return mas
213 | }()
214 |
215 | let sut = NSAttributedString {
216 | AText("Hello world")
217 | .stroke(width: -2)
218 | AText(" with Swift")
219 | }
220 |
221 | XCTAssertEqual(sut, testData)
222 | }
223 |
224 | func testModifyStrokeWithColor() {
225 | let testData: NSAttributedString = {
226 | let mas = NSMutableAttributedString(string: "Hello world",
227 | attributes: [.strokeWidth: -2,
228 | .strokeColor: Color.green])
229 | mas.append(NSAttributedString(string: " with Swift"))
230 | return mas
231 | }()
232 |
233 | let sut = NSAttributedString {
234 | AText("Hello world")
235 | .stroke(width: -2, color: .green)
236 | AText(" with Swift")
237 | }
238 |
239 | XCTAssertEqual(sut, testData)
240 | }
241 |
242 | func testModifyTextEffect() {
243 | let testData: NSAttributedString = {
244 | let mas = NSMutableAttributedString(string: "Hello world",
245 | attributes: [.textEffect: NSAttributedString.TextEffectStyle.letterpressStyle])
246 | mas.append(NSAttributedString(string: " with Swift"))
247 | return mas
248 | }()
249 |
250 | let sut = NSAttributedString {
251 | AText("Hello world")
252 | .textEffect(.letterpressStyle)
253 | AText(" with Swift")
254 | }
255 |
256 | XCTAssertEqual(sut, testData)
257 | }
258 |
259 | func testModifyUnderline() {
260 | let testData: NSAttributedString = {
261 | let mas = NSMutableAttributedString(string: "Hello world",
262 | attributes: [.underlineStyle: NSUnderlineStyle.patternDashDotDot.rawValue])
263 | mas.append(NSAttributedString(string: " with Swift"))
264 | return mas
265 | }()
266 |
267 | let sut = NSAttributedString {
268 | AText("Hello world")
269 | .underline(.patternDashDotDot)
270 | AText(" with Swift")
271 | }
272 |
273 | XCTAssertEqual(sut, testData)
274 | }
275 |
276 | func testModifyUnderlineWithColor() {
277 | let testData: NSAttributedString = {
278 | let mas = NSMutableAttributedString(string: "Hello world",
279 | attributes: [.underlineStyle: NSUnderlineStyle.patternDashDotDot.rawValue,
280 | .underlineColor: Color.cyan])
281 | mas.append(NSAttributedString(string: " with Swift"))
282 | return mas
283 | }()
284 |
285 | let sut = NSAttributedString {
286 | AText("Hello world")
287 | .underline(.patternDashDotDot, color: .cyan)
288 | AText(" with Swift")
289 | }
290 |
291 | XCTAssertEqual(sut, testData)
292 | }
293 |
294 | func testModifyWritingDirection() {
295 | let testData: NSAttributedString = {
296 | let mas = NSMutableAttributedString(string: "Hello world",
297 | attributes: [.writingDirection: NSWritingDirection.rightToLeft.rawValue])
298 | mas.append(NSAttributedString(string: " with Swift"))
299 | return mas
300 | }()
301 |
302 | let sut = NSAttributedString {
303 | AText("Hello world")
304 | .writingDirection(.rightToLeft)
305 | AText(" with Swift")
306 | }
307 |
308 | XCTAssertEqual(sut, testData)
309 | }
310 |
311 | #if canImport(AppKit)
312 | func testModifyVertical() {
313 | let testData: NSAttributedString = {
314 | let mas = NSMutableAttributedString(string: "Hello world",
315 | attributes: [.verticalGlyphForm: 1])
316 | mas.append(NSAttributedString(string: " with Swift"))
317 | return mas
318 | }()
319 |
320 | let sut = NSAttributedString {
321 | AText("Hello world")
322 | .vertical()
323 | AText(" with Swift")
324 | }
325 |
326 | XCTAssertEqual(sut, testData)
327 | }
328 | #endif
329 |
330 | func testChaining() {
331 | let testData: NSAttributedString = {
332 | let shadow = NSShadow()
333 | shadow.shadowColor = Color.black
334 | shadow.shadowBlurRadius = 10
335 | shadow.shadowOffset = .init(width: 4, height: 4)
336 |
337 | let mas = NSMutableAttributedString(
338 | string: "Hello world",
339 | attributes: [.backgroundColor: Color.red,
340 | .baselineOffset: 10,
341 | .font: Font.systemFont(ofSize: 20),
342 | .foregroundColor: Color.yellow,
343 | .expansion: 1,
344 | .kern: 3,
345 | .ligature: 0,
346 | .obliqueness: 0.5,
347 | .shadow: shadow,
348 | .strikethroughStyle: NSUnderlineStyle.patternDash.rawValue,
349 | .strikethroughColor: Color.black,
350 | .strokeWidth: -2,
351 | .strokeColor: Color.green,
352 | .textEffect: NSAttributedString.TextEffectStyle.letterpressStyle,
353 | .underlineStyle: NSUnderlineStyle.patternDashDotDot.rawValue,
354 | .underlineColor: Color.cyan,
355 | .writingDirection: NSWritingDirection.rightToLeft.rawValue]
356 | )
357 | mas.append(NSAttributedString(string: " with Swift"))
358 | return mas
359 | }()
360 |
361 | let sut = NSAttributedString {
362 | AText("Hello world")
363 | .backgroundColor(.red)
364 | .baselineOffset(10)
365 | .font(.systemFont(ofSize: 20))
366 | .foregroundColor(.yellow)
367 | .expansion(1)
368 | .kerning(3)
369 | .ligature(.none)
370 | .obliqueness(0.5)
371 | .shadow(color: .black, radius: 10, x: 4, y: 4)
372 | .strikethrough(style: .patternDash, color: .black)
373 | .stroke(width: -2, color: .green)
374 | .textEffect(.letterpressStyle)
375 | .underline(.patternDashDotDot, color: .cyan)
376 | .writingDirection(.rightToLeft)
377 | AText(" with Swift")
378 | }
379 |
380 | XCTAssertEqual(sut, testData)
381 | }
382 | }
383 |
--------------------------------------------------------------------------------
/Tests/NSAttributedStringBuilderTests/ComponentParagraphSylteModifierTests.swift:
--------------------------------------------------------------------------------
1 | @testable import NSAttributedStringBuilder
2 | import XCTest
3 |
4 | #if canImport(UIKit)
5 | import Foundation
6 | import UIKit
7 | #elseif canImport(AppKit)
8 | import AppKit
9 | #endif
10 |
11 | final class ComponentParagraphSylteModifierTests: XCTestCase {
12 | func testSetEmptyParagraphStyle() {
13 | let testData: NSAttributedString = {
14 | let ps = NSParagraphStyle()
15 | let mas = NSMutableAttributedString(string: "Hello world",
16 | attributes: [.paragraphStyle: ps])
17 | mas.append(NSAttributedString(string: " with Swift"))
18 | return mas
19 | }()
20 |
21 | let ps = NSParagraphStyle()
22 |
23 | let sut = NSAttributedString {
24 | AText("Hello world")
25 | .paragraphStyle(ps)
26 | AText(" with Swift")
27 | }
28 |
29 | XCTAssertEqual(sut, testData)
30 | }
31 |
32 | func testModifyMutableParagraphStyle() {
33 | let testData: NSAttributedString = {
34 | let paragraphStyle = NSMutableParagraphStyle()
35 | paragraphStyle.alignment = .right
36 |
37 | let mas = NSMutableAttributedString(string: "Hello world",
38 | attributes: [.paragraphStyle: paragraphStyle])
39 | mas.append(NSAttributedString(string: " with Swift"))
40 | return mas
41 | }()
42 |
43 | let mps = NSMutableParagraphStyle()
44 | mps.alignment = .right
45 |
46 | let sut = NSAttributedString {
47 | AText("Hello world")
48 | .paragraphStyle(mps)
49 | AText(" with Swift")
50 | }
51 |
52 | XCTAssertEqual(sut, testData)
53 | }
54 |
55 | func testModifyAlignment() {
56 | let testData: NSAttributedString = {
57 | let paragraphStyle = NSMutableParagraphStyle()
58 | paragraphStyle.alignment = .right
59 |
60 | let mas = NSMutableAttributedString(string: "Hello world",
61 | attributes: [.paragraphStyle: paragraphStyle])
62 | mas.append(NSAttributedString(string: " with Swift"))
63 | return mas
64 | }()
65 |
66 | let sut = NSAttributedString {
67 | AText("Hello world")
68 | .alignment(.right)
69 | AText(" with Swift")
70 | }
71 |
72 | XCTAssertEqual(sut, testData)
73 | }
74 |
75 | func testModifyFirstHeadIndent() {
76 | let testData: NSAttributedString = {
77 | let paragraphStyle = NSMutableParagraphStyle()
78 | paragraphStyle.firstLineHeadIndent = 16
79 |
80 | let mas = NSMutableAttributedString(string: "Hello world",
81 | attributes: [.paragraphStyle: paragraphStyle])
82 | mas.append(NSAttributedString(string: " with Swift"))
83 | return mas
84 | }()
85 |
86 | let sut = NSAttributedString {
87 | AText("Hello world")
88 | .firstLineHeadIndent(16)
89 | AText(" with Swift")
90 | }
91 |
92 | XCTAssertEqual(sut, testData)
93 | }
94 |
95 | func testModifyHeadIndent() {
96 | let testData: NSAttributedString = {
97 | let paragraphStyle = NSMutableParagraphStyle()
98 | paragraphStyle.headIndent = 13
99 |
100 | let mas = NSMutableAttributedString(string: "Hello world",
101 | attributes: [.paragraphStyle: paragraphStyle])
102 | mas.append(NSAttributedString(string: " with Swift"))
103 | return mas
104 | }()
105 |
106 | let sut = NSAttributedString {
107 | AText("Hello world")
108 | .headIndent(13)
109 | AText(" with Swift")
110 | }
111 |
112 | XCTAssertEqual(sut, testData)
113 | }
114 |
115 | func testModifyTailIndent() {
116 | let testData: NSAttributedString = {
117 | let paragraphStyle = NSMutableParagraphStyle()
118 | paragraphStyle.tailIndent = 19
119 |
120 | let mas = NSMutableAttributedString(string: "Hello world",
121 | attributes: [.paragraphStyle: paragraphStyle])
122 | mas.append(NSAttributedString(string: " with Swift"))
123 | return mas
124 | }()
125 |
126 | let sut = NSAttributedString {
127 | AText("Hello world")
128 | .tailIndent(19)
129 | AText(" with Swift")
130 | }
131 |
132 | XCTAssertEqual(sut, testData)
133 | }
134 |
135 | func testModifyLinebreakMode() {
136 | let testData: NSAttributedString = {
137 | let paragraphStyle = NSMutableParagraphStyle()
138 | paragraphStyle.lineBreakMode = .byWordWrapping
139 |
140 | let mas = NSMutableAttributedString(string: "Hello world",
141 | attributes: [.paragraphStyle: paragraphStyle])
142 | mas.append(NSAttributedString(string: " with Swift"))
143 | return mas
144 | }()
145 |
146 | let sut = NSAttributedString {
147 | AText("Hello world")
148 | .lineBreakeMode(.byWordWrapping)
149 | AText(" with Swift")
150 | }
151 |
152 | XCTAssertEqual(sut, testData)
153 | }
154 |
155 | func testModifyLineHeight() {
156 | let testData: NSAttributedString = {
157 | let paragraphStyle = NSMutableParagraphStyle()
158 | paragraphStyle.lineHeightMultiple = 1
159 | paragraphStyle.maximumLineHeight = 22
160 | paragraphStyle.minimumLineHeight = 18
161 |
162 | let mas = NSMutableAttributedString(string: "Hello world",
163 | attributes: [.paragraphStyle: paragraphStyle])
164 | mas.append(NSAttributedString(string: " with Swift"))
165 | return mas
166 | }()
167 |
168 | let sut = NSAttributedString {
169 | AText("Hello world")
170 | .lineHeight(multiple: 1, maximum: 22, minimum: 18)
171 | AText(" with Swift")
172 | }
173 |
174 | XCTAssertEqual(sut, testData)
175 | }
176 |
177 | func testModifyLineSpacing() {
178 | let testData: NSAttributedString = {
179 | let paragraphStyle = NSMutableParagraphStyle()
180 | paragraphStyle.lineSpacing = 7
181 |
182 | let mas = NSMutableAttributedString(string: "Hello world",
183 | attributes: [.paragraphStyle: paragraphStyle])
184 | mas.append(NSAttributedString(string: " with Swift"))
185 | return mas
186 | }()
187 |
188 | let sut = NSAttributedString {
189 | AText("Hello world")
190 | .lineSpacing(7)
191 | AText(" with Swift")
192 | }
193 |
194 | XCTAssertEqual(sut, testData)
195 | }
196 |
197 | func testModifyParagraphSpacing() {
198 | let testData: NSAttributedString = {
199 | let paragraphStyle = NSMutableParagraphStyle()
200 | paragraphStyle.paragraphSpacing = 9.3
201 | paragraphStyle.paragraphSpacingBefore = 17.2
202 |
203 | let mas = NSMutableAttributedString(string: "Hello world",
204 | attributes: [.paragraphStyle: paragraphStyle])
205 | mas.append(NSAttributedString(string: " with Swift"))
206 | return mas
207 | }()
208 |
209 | let sut = NSAttributedString {
210 | AText("Hello world")
211 | .paragraphSpacing(9.3, before: 17.2)
212 | AText(" with Swift")
213 | }
214 |
215 | XCTAssertEqual(sut, testData)
216 | }
217 |
218 | func testModifyBaseWritingDirection() {
219 | let testData: NSAttributedString = {
220 | let paragraphStyle = NSMutableParagraphStyle()
221 | paragraphStyle.baseWritingDirection = .rightToLeft
222 |
223 | let mas = NSMutableAttributedString(string: "Hello world",
224 | attributes: [.paragraphStyle: paragraphStyle])
225 | mas.append(NSAttributedString(string: " with Swift"))
226 | return mas
227 | }()
228 |
229 | let sut = NSAttributedString {
230 | AText("Hello world")
231 | .baseWritingDirection(.rightToLeft)
232 | AText(" with Swift")
233 | }
234 |
235 | XCTAssertEqual(sut, testData)
236 | }
237 |
238 | func testModifyHyphenationFactor() {
239 | let testData: NSAttributedString = {
240 | let paragraphStyle = NSMutableParagraphStyle()
241 | paragraphStyle.hyphenationFactor = 0.3
242 |
243 | let mas = NSMutableAttributedString(string: "Hello world",
244 | attributes: [.paragraphStyle: paragraphStyle])
245 | mas.append(NSAttributedString(string: " with Swift"))
246 | return mas
247 | }()
248 |
249 | let sut = NSAttributedString {
250 | AText("Hello world")
251 | .hyphenationFactor(0.3)
252 | AText(" with Swift")
253 | }
254 |
255 | XCTAssertEqual(sut, testData)
256 | }
257 |
258 | @available(iOS 9.0, tvOS 9.0, watchOS 2.0, OSX 10.11, *)
259 | func testAllowsDefaultTighteningForTruncation() {
260 | let testData: NSAttributedString = {
261 | let paragraphStyle = NSMutableParagraphStyle()
262 | paragraphStyle.allowsDefaultTighteningForTruncation = true
263 |
264 | let mas = NSMutableAttributedString(string: "Hello world",
265 | attributes: [.paragraphStyle: paragraphStyle])
266 | mas.append(NSAttributedString(string: " with Swift"))
267 | return mas
268 | }()
269 |
270 | let sut = NSAttributedString {
271 | AText("Hello world")
272 | .allowsDefaultTighteningForTruncation()
273 | AText(" with Swift")
274 | }
275 |
276 | XCTAssertEqual(sut, testData)
277 | }
278 |
279 | func testModifyTabStops() {
280 | let testData: NSAttributedString = {
281 | let paragraphStyle = NSMutableParagraphStyle()
282 | paragraphStyle.tabStops = [NSTextTab(textAlignment: .left, location: 6, options: [:]),
283 | NSTextTab(textAlignment: .right, location: 4, options: [:])]
284 |
285 | let mas = NSMutableAttributedString(string: "Hello world",
286 | attributes: [.paragraphStyle: paragraphStyle])
287 | mas.append(NSAttributedString(string: " with Swift"))
288 | return mas
289 | }()
290 |
291 | let sut = NSAttributedString {
292 | AText("Hello world")
293 | .tabsStops([NSTextTab(textAlignment: .left, location: 6, options: [:]),
294 | NSTextTab(textAlignment: .right, location: 4, options: [:])])
295 | AText(" with Swift")
296 | }
297 |
298 | XCTAssertEqual(sut, testData)
299 | }
300 |
301 | #if canImport(AppKit) && !targetEnvironment(macCatalyst)
302 |
303 | func testModifyTextBlocks() {
304 | let textBlock = NSTextBlock()
305 | textBlock.setWidth(30, type: .absoluteValueType, for: .border)
306 |
307 | let testData: NSAttributedString = {
308 | let paragraphStyle = NSMutableParagraphStyle()
309 | paragraphStyle.textBlocks = [textBlock]
310 |
311 | let mas = NSMutableAttributedString(string: "Hello world",
312 | attributes: [.paragraphStyle: paragraphStyle])
313 | mas.append(NSAttributedString(string: " with Swift"))
314 | return mas
315 | }()
316 |
317 | let sut = NSAttributedString {
318 | AText("Hello world")
319 | .textBlocks([textBlock])
320 | AText(" with Swift")
321 | }
322 |
323 | XCTAssertEqual(sut, testData)
324 | }
325 |
326 | @available(OSX 10.13, *)
327 | func testModifyTextList() {
328 | let textList = NSTextList(markerFormat: .box, options: 0)
329 |
330 | let testData: NSAttributedString = {
331 | let paragraphStyle = NSMutableParagraphStyle()
332 | paragraphStyle.textLists = [textList]
333 |
334 | let mas = NSMutableAttributedString(string: "Hello world",
335 | attributes: [.paragraphStyle: paragraphStyle])
336 | mas.append(NSAttributedString(string: " with Swift"))
337 | return mas
338 | }()
339 |
340 | let sut = NSAttributedString {
341 | AText("Hello world")
342 | .textLists([textList])
343 | AText(" with Swift")
344 | }
345 |
346 | XCTAssertEqual(sut, testData)
347 | }
348 |
349 | func testModifyTighteningFactorForTruncation() {
350 | let testData: NSAttributedString = {
351 | let paragraphStyle = NSMutableParagraphStyle()
352 | paragraphStyle.tighteningFactorForTruncation = 0.5
353 |
354 | let mas = NSMutableAttributedString(string: "Hello world",
355 | attributes: [.paragraphStyle: paragraphStyle])
356 | mas.append(NSAttributedString(string: " with Swift"))
357 | return mas
358 | }()
359 |
360 | let sut = NSAttributedString {
361 | AText("Hello world")
362 | .tighteningFactorForTruncation(0.5)
363 | AText(" with Swift")
364 | }
365 |
366 | XCTAssertEqual(sut, testData)
367 | }
368 |
369 | func testModifyHeaderLevel() {
370 | let testData: NSAttributedString = {
371 | let paragraphStyle = NSMutableParagraphStyle()
372 | paragraphStyle.headerLevel = 2
373 |
374 | let mas = NSMutableAttributedString(string: "Hello world",
375 | attributes: [.paragraphStyle: paragraphStyle])
376 | mas.append(NSAttributedString(string: " with Swift"))
377 | return mas
378 | }()
379 |
380 | let sut = NSAttributedString {
381 | AText("Hello world")
382 | .headerLevel(2)
383 | AText(" with Swift")
384 | }
385 |
386 | XCTAssertEqual(sut, testData)
387 | }
388 | #endif
389 |
390 | @available(iOS 9.0, tvOS 9.0, watchOS 2.0, OSX 10.11, *)
391 | func testChaining() {
392 | let testData: NSAttributedString = {
393 | let paragraphStyle = NSMutableParagraphStyle()
394 | paragraphStyle.alignment = .right
395 | paragraphStyle.firstLineHeadIndent = 16
396 | paragraphStyle.headIndent = 13
397 | paragraphStyle.tailIndent = 19
398 | paragraphStyle.lineBreakMode = .byWordWrapping
399 | paragraphStyle.lineHeightMultiple = 1
400 | paragraphStyle.maximumLineHeight = 22
401 | paragraphStyle.minimumLineHeight = 18
402 | paragraphStyle.lineSpacing = 7
403 | paragraphStyle.paragraphSpacing = 9.3
404 | paragraphStyle.paragraphSpacingBefore = 17.2
405 | paragraphStyle.baseWritingDirection = .rightToLeft
406 | paragraphStyle.hyphenationFactor = 0.3
407 | paragraphStyle.allowsDefaultTighteningForTruncation = true
408 |
409 | let mas = NSMutableAttributedString(string: "Hello world",
410 | attributes: [.paragraphStyle: paragraphStyle])
411 | mas.append(NSAttributedString(string: " with Swift"))
412 | return mas
413 | }()
414 |
415 | let sut = NSAttributedString {
416 | AText("Hello world")
417 | .alignment(.right)
418 | .firstLineHeadIndent(16)
419 | .headIndent(13)
420 | .tailIndent(19)
421 | .lineBreakeMode(.byWordWrapping)
422 | .lineHeight(multiple: 1, maximum: 22, minimum: 18)
423 | .lineSpacing(7)
424 | .paragraphSpacing(9.3, before: 17.2)
425 | .baseWritingDirection(.rightToLeft)
426 | .hyphenationFactor(0.3)
427 | .allowsDefaultTighteningForTruncation()
428 | AText(" with Swift")
429 | }
430 |
431 | XCTAssertEqual(sut, testData)
432 | }
433 |
434 | @available(iOS 9.0, tvOS 9.0, watchOS 2.0, OSX 10.11, *)
435 | func testRandomChainingOrderEqualness() {
436 | let sut = NSAttributedString {
437 | AText("Hello world")
438 | .alignment(.right)
439 | .firstLineHeadIndent(16)
440 | .headIndent(13)
441 | .tailIndent(19)
442 | .lineBreakeMode(.byWordWrapping)
443 | .lineHeight(multiple: 1, maximum: 22, minimum: 18)
444 | .lineSpacing(7)
445 | .paragraphSpacing(9.3, before: 17.2)
446 | .baseWritingDirection(.rightToLeft)
447 | .hyphenationFactor(0.3)
448 | .allowsDefaultTighteningForTruncation()
449 | AText(" with Swift")
450 | }
451 |
452 | let sut2 = NSAttributedString {
453 | AText("Hello world")
454 | .firstLineHeadIndent(16)
455 | .headIndent(13)
456 | .alignment(.right)
457 | .allowsDefaultTighteningForTruncation()
458 | .tailIndent(19)
459 | .lineSpacing(7)
460 | .lineBreakeMode(.byWordWrapping)
461 | .hyphenationFactor(0.3)
462 | .lineHeight(multiple: 1, maximum: 22, minimum: 18)
463 | .paragraphSpacing(9.3, before: 17.2)
464 | .baseWritingDirection(.rightToLeft)
465 | AText(" with Swift")
466 | }
467 |
468 | XCTAssertTrue(sut.isEqual(sut2))
469 | }
470 |
471 | func testSetEmptyParagraphStyleThenChaining() {
472 | let testData: NSAttributedString = {
473 | let mps = NSMutableParagraphStyle()
474 | mps.alignment = .justified
475 | let mas = NSMutableAttributedString(string: "Hello world",
476 | attributes: [.paragraphStyle: mps])
477 | mas.append(NSAttributedString(string: " with Swift"))
478 | return mas
479 | }()
480 |
481 | let ps = NSParagraphStyle()
482 |
483 | let sut = NSAttributedString {
484 | AText("Hello world")
485 | .paragraphStyle(ps)
486 | .alignment(.justified)
487 | AText(" with Swift")
488 | }
489 |
490 | XCTAssertEqual(sut, testData)
491 | }
492 | }
493 |
--------------------------------------------------------------------------------
/SwiftUISampleApp/AttributedTextSample.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 52;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 6C4BCC9222D74C9700A4F8D7 /* ImageAttachmentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C4BCC9122D74C9700A4F8D7 /* ImageAttachmentTests.swift */; };
11 | 6C4BCC9322D74CAF00A4F8D7 /* Swift_logo_color_rgb.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 6C4BCC8122D74C0A00A4F8D7 /* Swift_logo_color_rgb.jpg */; };
12 | 6C4BCC9422D74D8200A4F8D7 /* Swift_logo_color_rgb.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 6C4BCC8122D74C0A00A4F8D7 /* Swift_logo_color_rgb.jpg */; };
13 | 6C65EA2822D489A300EF32FB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C65EA2722D489A300EF32FB /* AppDelegate.swift */; };
14 | 6C65EA2A22D489A300EF32FB /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C65EA2922D489A300EF32FB /* SceneDelegate.swift */; };
15 | 6C65EA2C22D489A300EF32FB /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C65EA2B22D489A300EF32FB /* ContentView.swift */; };
16 | 6C65EA2E22D489A400EF32FB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6C65EA2D22D489A400EF32FB /* Assets.xcassets */; };
17 | 6C65EA3122D489A400EF32FB /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6C65EA3022D489A400EF32FB /* Preview Assets.xcassets */; };
18 | 6C65EA3422D489A400EF32FB /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6C65EA3222D489A400EF32FB /* LaunchScreen.storyboard */; };
19 | 6CF300AA22D48AC200D8C7DF /* NSAttributedStringBuilder in Frameworks */ = {isa = PBXBuildFile; productRef = 6CF300A922D48AC200D8C7DF /* NSAttributedStringBuilder */; };
20 | 6CF300B522D4A36600D8C7DF /* AttributedText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6CF300B422D4A36600D8C7DF /* AttributedText.swift */; };
21 | /* End PBXBuildFile section */
22 |
23 | /* Begin PBXContainerItemProxy section */
24 | 6C4BCC8C22D74C8D00A4F8D7 /* PBXContainerItemProxy */ = {
25 | isa = PBXContainerItemProxy;
26 | containerPortal = 6C65EA1C22D489A200EF32FB /* Project object */;
27 | proxyType = 1;
28 | remoteGlobalIDString = 6C65EA2322D489A300EF32FB;
29 | remoteInfo = AttributedTextSample;
30 | };
31 | /* End PBXContainerItemProxy section */
32 |
33 | /* Begin PBXFileReference section */
34 | 6C456A1122D6EEAE00A02325 /* NSAttributedStringBuilder */ = {isa = PBXFileReference; lastKnownFileType = folder; name = NSAttributedStringBuilder; path = ..; sourceTree = ""; };
35 | 6C4BCC8122D74C0A00A4F8D7 /* Swift_logo_color_rgb.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = Swift_logo_color_rgb.jpg; sourceTree = ""; };
36 | 6C4BCC8722D74C8D00A4F8D7 /* AttributedTextSampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AttributedTextSampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
37 | 6C4BCC8B22D74C8D00A4F8D7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
38 | 6C4BCC9122D74C9700A4F8D7 /* ImageAttachmentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageAttachmentTests.swift; sourceTree = ""; };
39 | 6C65EA2422D489A300EF32FB /* AttributedTextSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AttributedTextSample.app; sourceTree = BUILT_PRODUCTS_DIR; };
40 | 6C65EA2722D489A300EF32FB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
41 | 6C65EA2922D489A300EF32FB /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
42 | 6C65EA2B22D489A300EF32FB /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; };
43 | 6C65EA2D22D489A400EF32FB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
44 | 6C65EA3022D489A400EF32FB /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
45 | 6C65EA3322D489A400EF32FB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
46 | 6C65EA3522D489A400EF32FB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
47 | 6CF300AB22D48EA900D8C7DF /* AttributedTextSample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AttributedTextSample.entitlements; sourceTree = ""; };
48 | 6CF300B422D4A36600D8C7DF /* AttributedText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttributedText.swift; sourceTree = ""; };
49 | /* End PBXFileReference section */
50 |
51 | /* Begin PBXFrameworksBuildPhase section */
52 | 6C4BCC8422D74C8D00A4F8D7 /* Frameworks */ = {
53 | isa = PBXFrameworksBuildPhase;
54 | buildActionMask = 2147483647;
55 | files = (
56 | );
57 | runOnlyForDeploymentPostprocessing = 0;
58 | };
59 | 6C65EA2122D489A300EF32FB /* Frameworks */ = {
60 | isa = PBXFrameworksBuildPhase;
61 | buildActionMask = 2147483647;
62 | files = (
63 | 6CF300AA22D48AC200D8C7DF /* NSAttributedStringBuilder in Frameworks */,
64 | );
65 | runOnlyForDeploymentPostprocessing = 0;
66 | };
67 | /* End PBXFrameworksBuildPhase section */
68 |
69 | /* Begin PBXGroup section */
70 | 6C4BCC8822D74C8D00A4F8D7 /* AttributedTextSampleTests */ = {
71 | isa = PBXGroup;
72 | children = (
73 | 6C4BCC9122D74C9700A4F8D7 /* ImageAttachmentTests.swift */,
74 | 6C4BCC8122D74C0A00A4F8D7 /* Swift_logo_color_rgb.jpg */,
75 | 6C4BCC8B22D74C8D00A4F8D7 /* Info.plist */,
76 | );
77 | path = AttributedTextSampleTests;
78 | sourceTree = "";
79 | };
80 | 6C65EA1B22D489A200EF32FB = {
81 | isa = PBXGroup;
82 | children = (
83 | 6C456A1122D6EEAE00A02325 /* NSAttributedStringBuilder */,
84 | 6C65EA2622D489A300EF32FB /* AttributedTextSample */,
85 | 6C4BCC8822D74C8D00A4F8D7 /* AttributedTextSampleTests */,
86 | 6C65EA2522D489A300EF32FB /* Products */,
87 | 6CF300A822D48AC200D8C7DF /* Frameworks */,
88 | );
89 | sourceTree = "";
90 | };
91 | 6C65EA2522D489A300EF32FB /* Products */ = {
92 | isa = PBXGroup;
93 | children = (
94 | 6C65EA2422D489A300EF32FB /* AttributedTextSample.app */,
95 | 6C4BCC8722D74C8D00A4F8D7 /* AttributedTextSampleTests.xctest */,
96 | );
97 | name = Products;
98 | sourceTree = "";
99 | };
100 | 6C65EA2622D489A300EF32FB /* AttributedTextSample */ = {
101 | isa = PBXGroup;
102 | children = (
103 | 6CF300AB22D48EA900D8C7DF /* AttributedTextSample.entitlements */,
104 | 6CF300B422D4A36600D8C7DF /* AttributedText.swift */,
105 | 6C65EA2722D489A300EF32FB /* AppDelegate.swift */,
106 | 6C65EA2922D489A300EF32FB /* SceneDelegate.swift */,
107 | 6C65EA2B22D489A300EF32FB /* ContentView.swift */,
108 | 6C65EA2D22D489A400EF32FB /* Assets.xcassets */,
109 | 6C65EA3222D489A400EF32FB /* LaunchScreen.storyboard */,
110 | 6C65EA3522D489A400EF32FB /* Info.plist */,
111 | 6C65EA2F22D489A400EF32FB /* Preview Content */,
112 | );
113 | path = AttributedTextSample;
114 | sourceTree = "";
115 | };
116 | 6C65EA2F22D489A400EF32FB /* Preview Content */ = {
117 | isa = PBXGroup;
118 | children = (
119 | 6C65EA3022D489A400EF32FB /* Preview Assets.xcassets */,
120 | );
121 | path = "Preview Content";
122 | sourceTree = "";
123 | };
124 | 6CF300A822D48AC200D8C7DF /* Frameworks */ = {
125 | isa = PBXGroup;
126 | children = (
127 | );
128 | name = Frameworks;
129 | sourceTree = "";
130 | };
131 | /* End PBXGroup section */
132 |
133 | /* Begin PBXNativeTarget section */
134 | 6C4BCC8622D74C8D00A4F8D7 /* AttributedTextSampleTests */ = {
135 | isa = PBXNativeTarget;
136 | buildConfigurationList = 6C4BCC8E22D74C8D00A4F8D7 /* Build configuration list for PBXNativeTarget "AttributedTextSampleTests" */;
137 | buildPhases = (
138 | 6C4BCC8322D74C8D00A4F8D7 /* Sources */,
139 | 6C4BCC8422D74C8D00A4F8D7 /* Frameworks */,
140 | 6C4BCC8522D74C8D00A4F8D7 /* Resources */,
141 | );
142 | buildRules = (
143 | );
144 | dependencies = (
145 | 6C4BCC8D22D74C8D00A4F8D7 /* PBXTargetDependency */,
146 | );
147 | name = AttributedTextSampleTests;
148 | productName = AttributedTextSampleTests;
149 | productReference = 6C4BCC8722D74C8D00A4F8D7 /* AttributedTextSampleTests.xctest */;
150 | productType = "com.apple.product-type.bundle.unit-test";
151 | };
152 | 6C65EA2322D489A300EF32FB /* AttributedTextSample */ = {
153 | isa = PBXNativeTarget;
154 | buildConfigurationList = 6C65EA3822D489A400EF32FB /* Build configuration list for PBXNativeTarget "AttributedTextSample" */;
155 | buildPhases = (
156 | 6C65EA2022D489A300EF32FB /* Sources */,
157 | 6C65EA2122D489A300EF32FB /* Frameworks */,
158 | 6C65EA2222D489A300EF32FB /* Resources */,
159 | );
160 | buildRules = (
161 | );
162 | dependencies = (
163 | );
164 | name = AttributedTextSample;
165 | packageProductDependencies = (
166 | 6CF300A922D48AC200D8C7DF /* NSAttributedStringBuilder */,
167 | );
168 | productName = AttributedTextSample;
169 | productReference = 6C65EA2422D489A300EF32FB /* AttributedTextSample.app */;
170 | productType = "com.apple.product-type.application";
171 | };
172 | /* End PBXNativeTarget section */
173 |
174 | /* Begin PBXProject section */
175 | 6C65EA1C22D489A200EF32FB /* Project object */ = {
176 | isa = PBXProject;
177 | attributes = {
178 | LastSwiftUpdateCheck = 1100;
179 | LastUpgradeCheck = 1100;
180 | ORGANIZATIONNAME = "Elaborapp Co., Ltd.";
181 | TargetAttributes = {
182 | 6C4BCC8622D74C8D00A4F8D7 = {
183 | CreatedOnToolsVersion = 11.0;
184 | TestTargetID = 6C65EA2322D489A300EF32FB;
185 | };
186 | 6C65EA2322D489A300EF32FB = {
187 | CreatedOnToolsVersion = 11.0;
188 | };
189 | };
190 | };
191 | buildConfigurationList = 6C65EA1F22D489A200EF32FB /* Build configuration list for PBXProject "AttributedTextSample" */;
192 | compatibilityVersion = "Xcode 9.3";
193 | developmentRegion = en;
194 | hasScannedForEncodings = 0;
195 | knownRegions = (
196 | en,
197 | Base,
198 | );
199 | mainGroup = 6C65EA1B22D489A200EF32FB;
200 | packageReferences = (
201 | );
202 | productRefGroup = 6C65EA2522D489A300EF32FB /* Products */;
203 | projectDirPath = "";
204 | projectRoot = "";
205 | targets = (
206 | 6C65EA2322D489A300EF32FB /* AttributedTextSample */,
207 | 6C4BCC8622D74C8D00A4F8D7 /* AttributedTextSampleTests */,
208 | );
209 | };
210 | /* End PBXProject section */
211 |
212 | /* Begin PBXResourcesBuildPhase section */
213 | 6C4BCC8522D74C8D00A4F8D7 /* Resources */ = {
214 | isa = PBXResourcesBuildPhase;
215 | buildActionMask = 2147483647;
216 | files = (
217 | 6C4BCC9322D74CAF00A4F8D7 /* Swift_logo_color_rgb.jpg in Resources */,
218 | );
219 | runOnlyForDeploymentPostprocessing = 0;
220 | };
221 | 6C65EA2222D489A300EF32FB /* Resources */ = {
222 | isa = PBXResourcesBuildPhase;
223 | buildActionMask = 2147483647;
224 | files = (
225 | 6C4BCC9422D74D8200A4F8D7 /* Swift_logo_color_rgb.jpg in Resources */,
226 | 6C65EA3422D489A400EF32FB /* LaunchScreen.storyboard in Resources */,
227 | 6C65EA3122D489A400EF32FB /* Preview Assets.xcassets in Resources */,
228 | 6C65EA2E22D489A400EF32FB /* Assets.xcassets in Resources */,
229 | );
230 | runOnlyForDeploymentPostprocessing = 0;
231 | };
232 | /* End PBXResourcesBuildPhase section */
233 |
234 | /* Begin PBXSourcesBuildPhase section */
235 | 6C4BCC8322D74C8D00A4F8D7 /* Sources */ = {
236 | isa = PBXSourcesBuildPhase;
237 | buildActionMask = 2147483647;
238 | files = (
239 | 6C4BCC9222D74C9700A4F8D7 /* ImageAttachmentTests.swift in Sources */,
240 | );
241 | runOnlyForDeploymentPostprocessing = 0;
242 | };
243 | 6C65EA2022D489A300EF32FB /* Sources */ = {
244 | isa = PBXSourcesBuildPhase;
245 | buildActionMask = 2147483647;
246 | files = (
247 | 6CF300B522D4A36600D8C7DF /* AttributedText.swift in Sources */,
248 | 6C65EA2822D489A300EF32FB /* AppDelegate.swift in Sources */,
249 | 6C65EA2A22D489A300EF32FB /* SceneDelegate.swift in Sources */,
250 | 6C65EA2C22D489A300EF32FB /* ContentView.swift in Sources */,
251 | );
252 | runOnlyForDeploymentPostprocessing = 0;
253 | };
254 | /* End PBXSourcesBuildPhase section */
255 |
256 | /* Begin PBXTargetDependency section */
257 | 6C4BCC8D22D74C8D00A4F8D7 /* PBXTargetDependency */ = {
258 | isa = PBXTargetDependency;
259 | target = 6C65EA2322D489A300EF32FB /* AttributedTextSample */;
260 | targetProxy = 6C4BCC8C22D74C8D00A4F8D7 /* PBXContainerItemProxy */;
261 | };
262 | /* End PBXTargetDependency section */
263 |
264 | /* Begin PBXVariantGroup section */
265 | 6C65EA3222D489A400EF32FB /* LaunchScreen.storyboard */ = {
266 | isa = PBXVariantGroup;
267 | children = (
268 | 6C65EA3322D489A400EF32FB /* Base */,
269 | );
270 | name = LaunchScreen.storyboard;
271 | sourceTree = "";
272 | };
273 | /* End PBXVariantGroup section */
274 |
275 | /* Begin XCBuildConfiguration section */
276 | 6C4BCC8F22D74C8D00A4F8D7 /* Debug */ = {
277 | isa = XCBuildConfiguration;
278 | buildSettings = {
279 | BUNDLE_LOADER = "$(TEST_HOST)";
280 | CODE_SIGN_STYLE = Automatic;
281 | DEVELOPMENT_TEAM = SE9N6X5V7C;
282 | INFOPLIST_FILE = AttributedTextSampleTests/Info.plist;
283 | LD_RUNPATH_SEARCH_PATHS = (
284 | "$(inherited)",
285 | "@executable_path/Frameworks",
286 | "@loader_path/Frameworks",
287 | );
288 | PRODUCT_BUNDLE_IDENTIFIER = com.elaborapp.NSAttributedStringBuilder.AttributedTextSampleTests;
289 | PRODUCT_NAME = "$(TARGET_NAME)";
290 | SWIFT_VERSION = 5.0;
291 | TARGETED_DEVICE_FAMILY = "1,2";
292 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AttributedTextSample.app/AttributedTextSample";
293 | };
294 | name = Debug;
295 | };
296 | 6C4BCC9022D74C8D00A4F8D7 /* Release */ = {
297 | isa = XCBuildConfiguration;
298 | buildSettings = {
299 | BUNDLE_LOADER = "$(TEST_HOST)";
300 | CODE_SIGN_STYLE = Automatic;
301 | DEVELOPMENT_TEAM = SE9N6X5V7C;
302 | INFOPLIST_FILE = AttributedTextSampleTests/Info.plist;
303 | LD_RUNPATH_SEARCH_PATHS = (
304 | "$(inherited)",
305 | "@executable_path/Frameworks",
306 | "@loader_path/Frameworks",
307 | );
308 | PRODUCT_BUNDLE_IDENTIFIER = com.elaborapp.NSAttributedStringBuilder.AttributedTextSampleTests;
309 | PRODUCT_NAME = "$(TARGET_NAME)";
310 | SWIFT_VERSION = 5.0;
311 | TARGETED_DEVICE_FAMILY = "1,2";
312 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AttributedTextSample.app/AttributedTextSample";
313 | };
314 | name = Release;
315 | };
316 | 6C65EA3622D489A400EF32FB /* Debug */ = {
317 | isa = XCBuildConfiguration;
318 | buildSettings = {
319 | ALWAYS_SEARCH_USER_PATHS = NO;
320 | CLANG_ANALYZER_NONNULL = YES;
321 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
322 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
323 | CLANG_CXX_LIBRARY = "libc++";
324 | CLANG_ENABLE_MODULES = YES;
325 | CLANG_ENABLE_OBJC_ARC = YES;
326 | CLANG_ENABLE_OBJC_WEAK = YES;
327 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
328 | CLANG_WARN_BOOL_CONVERSION = YES;
329 | CLANG_WARN_COMMA = YES;
330 | CLANG_WARN_CONSTANT_CONVERSION = YES;
331 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
332 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
333 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
334 | CLANG_WARN_EMPTY_BODY = YES;
335 | CLANG_WARN_ENUM_CONVERSION = YES;
336 | CLANG_WARN_INFINITE_RECURSION = YES;
337 | CLANG_WARN_INT_CONVERSION = YES;
338 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
339 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
340 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
341 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
342 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
343 | CLANG_WARN_STRICT_PROTOTYPES = YES;
344 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
345 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
346 | CLANG_WARN_UNREACHABLE_CODE = YES;
347 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
348 | COPY_PHASE_STRIP = NO;
349 | DEBUG_INFORMATION_FORMAT = dwarf;
350 | ENABLE_STRICT_OBJC_MSGSEND = YES;
351 | ENABLE_TESTABILITY = YES;
352 | GCC_C_LANGUAGE_STANDARD = gnu11;
353 | GCC_DYNAMIC_NO_PIC = NO;
354 | GCC_NO_COMMON_BLOCKS = YES;
355 | GCC_OPTIMIZATION_LEVEL = 0;
356 | GCC_PREPROCESSOR_DEFINITIONS = (
357 | "DEBUG=1",
358 | "$(inherited)",
359 | );
360 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
361 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
362 | GCC_WARN_UNDECLARED_SELECTOR = YES;
363 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
364 | GCC_WARN_UNUSED_FUNCTION = YES;
365 | GCC_WARN_UNUSED_VARIABLE = YES;
366 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
367 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
368 | MTL_FAST_MATH = YES;
369 | ONLY_ACTIVE_ARCH = YES;
370 | SDKROOT = iphoneos;
371 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
372 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
373 | };
374 | name = Debug;
375 | };
376 | 6C65EA3722D489A400EF32FB /* Release */ = {
377 | isa = XCBuildConfiguration;
378 | buildSettings = {
379 | ALWAYS_SEARCH_USER_PATHS = NO;
380 | CLANG_ANALYZER_NONNULL = YES;
381 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
382 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
383 | CLANG_CXX_LIBRARY = "libc++";
384 | CLANG_ENABLE_MODULES = YES;
385 | CLANG_ENABLE_OBJC_ARC = YES;
386 | CLANG_ENABLE_OBJC_WEAK = YES;
387 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
388 | CLANG_WARN_BOOL_CONVERSION = YES;
389 | CLANG_WARN_COMMA = YES;
390 | CLANG_WARN_CONSTANT_CONVERSION = YES;
391 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
392 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
393 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
394 | CLANG_WARN_EMPTY_BODY = YES;
395 | CLANG_WARN_ENUM_CONVERSION = YES;
396 | CLANG_WARN_INFINITE_RECURSION = YES;
397 | CLANG_WARN_INT_CONVERSION = YES;
398 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
399 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
400 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
401 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
402 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
403 | CLANG_WARN_STRICT_PROTOTYPES = YES;
404 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
405 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
406 | CLANG_WARN_UNREACHABLE_CODE = YES;
407 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
408 | COPY_PHASE_STRIP = NO;
409 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
410 | ENABLE_NS_ASSERTIONS = NO;
411 | ENABLE_STRICT_OBJC_MSGSEND = YES;
412 | GCC_C_LANGUAGE_STANDARD = gnu11;
413 | GCC_NO_COMMON_BLOCKS = YES;
414 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
415 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
416 | GCC_WARN_UNDECLARED_SELECTOR = YES;
417 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
418 | GCC_WARN_UNUSED_FUNCTION = YES;
419 | GCC_WARN_UNUSED_VARIABLE = YES;
420 | IPHONEOS_DEPLOYMENT_TARGET = 13.0;
421 | MTL_ENABLE_DEBUG_INFO = NO;
422 | MTL_FAST_MATH = YES;
423 | SDKROOT = iphoneos;
424 | SWIFT_COMPILATION_MODE = wholemodule;
425 | SWIFT_OPTIMIZATION_LEVEL = "-O";
426 | VALIDATE_PRODUCT = YES;
427 | };
428 | name = Release;
429 | };
430 | 6C65EA3922D489A400EF32FB /* Debug */ = {
431 | isa = XCBuildConfiguration;
432 | buildSettings = {
433 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
434 | CODE_SIGN_ENTITLEMENTS = AttributedTextSample/AttributedTextSample.entitlements;
435 | CODE_SIGN_STYLE = Automatic;
436 | DEVELOPMENT_ASSET_PATHS = "AttributedTextSample/Preview\\ Content";
437 | DEVELOPMENT_TEAM = SE9N6X5V7C;
438 | ENABLE_PREVIEWS = YES;
439 | INFOPLIST_FILE = AttributedTextSample/Info.plist;
440 | LD_RUNPATH_SEARCH_PATHS = (
441 | "$(inherited)",
442 | "@executable_path/Frameworks",
443 | );
444 | PRODUCT_BUNDLE_IDENTIFIER = com.elaborapp.AttributedTextSample;
445 | PRODUCT_NAME = "$(TARGET_NAME)";
446 | SUPPORTS_UIKITFORMAC = NO;
447 | SWIFT_VERSION = 5.0;
448 | TARGETED_DEVICE_FAMILY = "1,2";
449 | };
450 | name = Debug;
451 | };
452 | 6C65EA3A22D489A400EF32FB /* Release */ = {
453 | isa = XCBuildConfiguration;
454 | buildSettings = {
455 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
456 | CODE_SIGN_ENTITLEMENTS = AttributedTextSample/AttributedTextSample.entitlements;
457 | CODE_SIGN_STYLE = Automatic;
458 | DEVELOPMENT_ASSET_PATHS = "AttributedTextSample/Preview\\ Content";
459 | DEVELOPMENT_TEAM = SE9N6X5V7C;
460 | ENABLE_PREVIEWS = YES;
461 | INFOPLIST_FILE = AttributedTextSample/Info.plist;
462 | LD_RUNPATH_SEARCH_PATHS = (
463 | "$(inherited)",
464 | "@executable_path/Frameworks",
465 | );
466 | PRODUCT_BUNDLE_IDENTIFIER = com.elaborapp.AttributedTextSample;
467 | PRODUCT_NAME = "$(TARGET_NAME)";
468 | SUPPORTS_UIKITFORMAC = NO;
469 | SWIFT_VERSION = 5.0;
470 | TARGETED_DEVICE_FAMILY = "1,2";
471 | };
472 | name = Release;
473 | };
474 | /* End XCBuildConfiguration section */
475 |
476 | /* Begin XCConfigurationList section */
477 | 6C4BCC8E22D74C8D00A4F8D7 /* Build configuration list for PBXNativeTarget "AttributedTextSampleTests" */ = {
478 | isa = XCConfigurationList;
479 | buildConfigurations = (
480 | 6C4BCC8F22D74C8D00A4F8D7 /* Debug */,
481 | 6C4BCC9022D74C8D00A4F8D7 /* Release */,
482 | );
483 | defaultConfigurationIsVisible = 0;
484 | defaultConfigurationName = Release;
485 | };
486 | 6C65EA1F22D489A200EF32FB /* Build configuration list for PBXProject "AttributedTextSample" */ = {
487 | isa = XCConfigurationList;
488 | buildConfigurations = (
489 | 6C65EA3622D489A400EF32FB /* Debug */,
490 | 6C65EA3722D489A400EF32FB /* Release */,
491 | );
492 | defaultConfigurationIsVisible = 0;
493 | defaultConfigurationName = Release;
494 | };
495 | 6C65EA3822D489A400EF32FB /* Build configuration list for PBXNativeTarget "AttributedTextSample" */ = {
496 | isa = XCConfigurationList;
497 | buildConfigurations = (
498 | 6C65EA3922D489A400EF32FB /* Debug */,
499 | 6C65EA3A22D489A400EF32FB /* Release */,
500 | );
501 | defaultConfigurationIsVisible = 0;
502 | defaultConfigurationName = Release;
503 | };
504 | /* End XCConfigurationList section */
505 |
506 | /* Begin XCSwiftPackageProductDependency section */
507 | 6CF300A922D48AC200D8C7DF /* NSAttributedStringBuilder */ = {
508 | isa = XCSwiftPackageProductDependency;
509 | productName = NSAttributedStringBuilder;
510 | };
511 | /* End XCSwiftPackageProductDependency section */
512 | };
513 | rootObject = 6C65EA1C22D489A200EF32FB /* Project object */;
514 | }
515 |
--------------------------------------------------------------------------------