diff --git a/Package.swift b/Package.swift index 92cd600..bcd6d85 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.1 +// swift-tools-version:5.4 import PackageDescription diff --git a/SwiftyAttributes.podspec b/SwiftyAttributes.podspec index 9df65ea..64aa5f5 100644 --- a/SwiftyAttributes.podspec +++ b/SwiftyAttributes.podspec @@ -26,5 +26,5 @@ Pod::Spec.new do |s| s.tvos.deployment_target = '9.0' s.watchos.deployment_target = '2.0' - s.swift_versions = ['5.0', '5.1'] + s.swift_versions = ['5.0', '5.1', '5.4'] end diff --git a/SwiftyAttributes.xcodeproj/project.pbxproj b/SwiftyAttributes.xcodeproj/project.pbxproj index 337acb9..1bf7a01 100644 --- a/SwiftyAttributes.xcodeproj/project.pbxproj +++ b/SwiftyAttributes.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 9E04C4D126EA09A5009BA429 /* SwiftyAttributedStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E04C4D026EA09A5009BA429 /* SwiftyAttributedStringBuilder.swift */; }; 9E0DA889229B2F8600E29BFD /* UIKit+SwiftyAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E0DA888229B2F8600E29BFD /* UIKit+SwiftyAttributes.swift */; }; 9E0DA88E229B34D800E29BFD /* UIKit+SwiftyAttributes_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E0DA88D229B34D800E29BFD /* UIKit+SwiftyAttributes_Tests.swift */; }; 9E28077022A424320097CE8A /* NSString+macOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E28076F22A424320097CE8A /* NSString+macOS.swift */; }; @@ -74,6 +75,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 9E04C4D026EA09A5009BA429 /* SwiftyAttributedStringBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SwiftyAttributedStringBuilder.swift; path = common/SwiftyAttributedStringBuilder.swift; sourceTree = ""; }; 9E0DA888229B2F8600E29BFD /* UIKit+SwiftyAttributes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIKit+SwiftyAttributes.swift"; sourceTree = ""; }; 9E0DA88D229B34D800E29BFD /* UIKit+SwiftyAttributes_Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIKit+SwiftyAttributes_Tests.swift"; sourceTree = ""; }; 9E28076F22A424320097CE8A /* NSString+macOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSString+macOS.swift"; sourceTree = ""; }; @@ -249,6 +251,7 @@ 9EB6D1A222A1CAC200D00CFC /* NSString+SwiftyAttributes.swift */, C05082241DFA72F700D39B3B /* Operators.swift */, C05082251DFA72F700D39B3B /* String+SwiftyAttributes.swift */, + 9E04C4D026EA09A5009BA429 /* SwiftyAttributedStringBuilder.swift */, C05082261DFA72F700D39B3B /* TextEffect.swift */, C05082271DFA72F700D39B3B /* VerticalGlyphForm.swift */, C05082281DFA72F700D39B3B /* WritingDirection.swift */, @@ -479,6 +482,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 9E04C4D126EA09A5009BA429 /* SwiftyAttributedStringBuilder.swift in Sources */, C050822A1DFA72F700D39B3B /* Attribute.swift in Sources */, C050822F1DFA72F700D39B3B /* Operators.swift in Sources */, C050822E1DFA72F700D39B3B /* NSMutableAttributedString+SwiftyAttributes.swift in Sources */, diff --git a/SwiftyAttributes/Sources/common/NSAttributedString+SwiftyAttributes.swift b/SwiftyAttributes/Sources/common/NSAttributedString+SwiftyAttributes.swift index 0b350e6..54bfaec 100644 --- a/SwiftyAttributes/Sources/common/NSAttributedString+SwiftyAttributes.swift +++ b/SwiftyAttributes/Sources/common/NSAttributedString+SwiftyAttributes.swift @@ -28,6 +28,18 @@ extension NSAttributedString { self.init(string: str, attributes: dictionary(from: attrs)) } + #if swift(>=5.4) + /** + Creates a new `NSAttributedString` with the specified attributes. + + - parameter str: The string for the new attributed string. + - parameter swiftyAttributes: The attributes for the new attributed string. + */ + public convenience init(string str: String, @SwiftyAttributedStringBuilder swiftyAttributes attrs: () -> [NSAttributedString.Key: Any]) { + self.init(string: str, attributes: attrs()) + } + #endif + /** Returns an attributed string with the specified attributes added. @@ -40,6 +52,20 @@ extension NSAttributedString { return mutable } + #if swift(>=5.4) + /** + Returns an attributed string with the specified attributes added. + + - parameter attributes: The attributes to add to the new attributed string. + - returns: An `NSMutableAttributedString` with the new attributes applied. + */ + public func withAttributes(@SwiftyAttributedStringBuilder _ attributes: () -> [NSAttributedString.Key: Any]) -> NSMutableAttributedString { + let mutable = mutableCopy() as! NSMutableAttributedString + mutable.addAttributes(attributes(), range: NSRange(location: 0, length: length)) + return mutable + } + #endif + /** Returns an attributed string with the specified attribute added. diff --git a/SwiftyAttributes/Sources/common/String+SwiftyAttributes.swift b/SwiftyAttributes/Sources/common/String+SwiftyAttributes.swift index 155b9f7..29c82c5 100644 --- a/SwiftyAttributes/Sources/common/String+SwiftyAttributes.swift +++ b/SwiftyAttributes/Sources/common/String+SwiftyAttributes.swift @@ -20,6 +20,18 @@ extension String { return attributedString.withAttributes(attributes) } + #if swift(>=5.4) + /** + Returns an attributed string with the specified attributes added. + + - parameter attributes: The attributes to add to the new attributed string. + - returns: An `NSMutableAttributedString` with the new attributes applied. + */ + public func withAttributes(@SwiftyAttributedStringBuilder _ attributes: () -> [NSAttributedString.Key: Any]) -> NSMutableAttributedString { + return attributedString.withAttributes(attributes) + } + #endif + /** Returns an attributed string with the specified attribute added. diff --git a/SwiftyAttributes/Sources/common/SwiftyAttributedStringBuilder.swift b/SwiftyAttributes/Sources/common/SwiftyAttributedStringBuilder.swift new file mode 100644 index 0000000..2f9cca7 --- /dev/null +++ b/SwiftyAttributes/Sources/common/SwiftyAttributedStringBuilder.swift @@ -0,0 +1,46 @@ +// +// SwiftyAttributedStringBuilder.swift +// SwiftyAttributes +// +// Created by Roman Podymov on 09/09/21. +// Copyright © 2021 Roman Podymov. All rights reserved. +// + +#if swift(>=5.4) +import Foundation + +public protocol AttributesProvider { + var attributes: [Attribute] { get } +} + +extension Attribute: AttributesProvider { + public var attributes: [Attribute] { [self] } +} + +extension Array: AttributesProvider where Element == Attribute { + public var attributes: [Attribute] { self } +} + +@resultBuilder +public struct SwiftyAttributedStringBuilder { + public static func buildBlock(_ components: AttributesProvider...) -> [Attribute] { + components.flatMap { $0.attributes } + } + + public static func buildEither(first components: [AttributesProvider]) -> [Attribute] { + components.flatMap { $0.attributes } + } + + public static func buildEither(second components: [AttributesProvider]) -> [Attribute] { + components.flatMap { $0.attributes } + } + + public static func buildOptional(_ components: [Attribute]?) -> [Attribute] { + components?.flatMap { $0.attributes } ?? [] + } + + public static func buildFinalResult(_ components: [Attribute]) -> [NSAttributedString.Key: Any] { + dictionary(from: components) + } +} +#endif diff --git a/SwiftyAttributesTests/NSAttributedString_Tests.swift b/SwiftyAttributesTests/NSAttributedString_Tests.swift index d600ef0..caa1d5f 100644 --- a/SwiftyAttributesTests/NSAttributedString_Tests.swift +++ b/SwiftyAttributesTests/NSAttributedString_Tests.swift @@ -17,6 +17,42 @@ class NSAttributedString_Tests: XCTestCase { XCTAssertEqual(subject, expected) } + #if swift(>=5.4) + func testInit_withSwiftyAttributedStringBuilder() { + let shouldAddStrokeWidth = true + let subject = NSAttributedString(string: "Hello World") { + Attribute.strokeColor(.green) + if let link = URL(string: "").map { Attribute.link($0) } { + link + } + if shouldAddStrokeWidth { + Attribute.strokeWidth(3) + } else { + Attribute.strokeColor(.yellow) + } + } + let expected = NSAttributedString(string: "Hello World", attributes: [.strokeColor: Color.green, .strokeWidth: 3]) + XCTAssertEqual(subject, expected) + } + + func testWithAttributes_SwiftyAttributedStringBuilder() { + let shouldAddStrokeWidth = true + let subject = NSAttributedString(string: "Hello World").withAttributes { + Attribute.strokeColor(.green) + if let link = URL(string: "").map { Attribute.link($0) } { + link + } + if shouldAddStrokeWidth { + Attribute.strokeWidth(3) + } else { + Attribute.strokeColor(.yellow) + } + } + let expected = NSAttributedString(string: "Hello World", attributes: [.strokeColor: Color.green, .strokeWidth: 3]) + XCTAssertEqual(subject, expected) + } + #endif + func testSubstringFromRange() { let str = "Hello".withStrikethroughColor(.blue).withUnderlineStyle(.patternDash) str.addAttributes([.backgroundColor(.brown)], range: 0 ..< 3) diff --git a/SwiftyAttributesTests/SwiftyAttributes_Tests.swift b/SwiftyAttributesTests/SwiftyAttributes_Tests.swift index 714b119..56ea69e 100644 --- a/SwiftyAttributesTests/SwiftyAttributes_Tests.swift +++ b/SwiftyAttributesTests/SwiftyAttributes_Tests.swift @@ -11,6 +11,25 @@ import SwiftyAttributes class SwiftyAttributesTests: XCTestCase { + #if swift(>=5.4) + func testString_withAttributes_SwiftyAttributedStringBuilder() { + let shouldAddStrokeWidth = true + let subject = "Hello".withAttributes { + Attribute.strokeColor(.green) + if let link = URL(string: "").map { Attribute.link($0) } { + link + } + if shouldAddStrokeWidth { + Attribute.strokeWidth(3) + } else { + Attribute.strokeColor(.yellow) + } + } + let expected = NSAttributedString(string: "Hello", attributes: [.strokeColor: Color.green, .strokeWidth: 3]) + XCTAssertEqual(subject, expected) + } + #endif + func testString_withAttribute() { let subject = "Hello".withAttribute(.strokeWidth(4)) let expected = NSAttributedString(string: "Hello", attributes: [.strokeWidth: NSNumber(value: 4)])