Skip to content

Commit

Permalink
Fix issue where setting attributedText could override properties (#10)
Browse files Browse the repository at this point in the history
* Fix issue where setting attributedText could override properties

There was a regression in 0.0.5 that caused properties that were set on
the label to overwrite the properties of the attributedString the user
was setting on attributedText. This fixes that regression by reverting
the changes that were made to the didSet block of attributedText. Since
there's a valid use-case for wanting to set properties on the label
and have them overwrite the properties inside the attributedstring, I've
added another function `setAttributedText(_ attributedString:
NSAttributedString,
afterInheritingLabelAttributesAndConfiguringWithBlock block:
((NSMutableAttributedString) -> NSMutableAttributedString)?)` to support
that.

* Don't run swiftlint on carthage builds

* Add description for new setAttributedString function
  • Loading branch information
chansen22 committed Mar 5, 2019
1 parent ebad4db commit fac29a0
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 10 deletions.
2 changes: 1 addition & 1 deletion Nantes/Nantes.xcodeproj/project.pbxproj
Expand Up @@ -211,7 +211,7 @@
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "swift run swiftlint --path .. --strict --quiet\n";
shellScript = "if [ -z \"$CARTHAGE\" ]\nthen\n swift run swiftlint --path .. --strict --quiet\nfi\n";
};
/* End PBXShellScriptBuildPhase section */

Expand Down
Expand Up @@ -26,6 +26,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
codeCoverageEnabled = "YES"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
Expand Down
80 changes: 80 additions & 0 deletions Nantes/NantesTests/NantesTests.swift
Expand Up @@ -151,6 +151,86 @@ final class NantesLabelTests: XCTestCase {
XCTAssertTrue(size == CGSize(width: 55.0, height: 15.0))
}

func testAttributedStringPropertiesStay() {
let paragraphStyle = getParagraphStyle()
let attributedString = NSAttributedString(string: "Test string with properties", attributes: [.paragraphStyle: paragraphStyle, .kern: 30])
addPropertiesToLabel(&label)

label.attributedText = attributedString

let updatedParagraphStyle = label.attributedText!.attribute(.paragraphStyle, at: 0, effectiveRange: nil) as! NSParagraphStyle
let updatedKern = label.attributedText!.attribute(.kern, at: 0, effectiveRange: nil) as! NSNumber

XCTAssertEqual(updatedParagraphStyle.lineSpacing, 20)
XCTAssertEqual(updatedParagraphStyle.minimumLineHeight, 30)
XCTAssertEqual(updatedParagraphStyle.maximumLineHeight, 40)
XCTAssertEqual(updatedKern.intValue, 30)
}

func testAttributedStringPropertiesUpdate() {
let paragraphStyle = getParagraphStyle()
let attributedString = NSAttributedString(string: "Test string with properties", attributes: [.paragraphStyle: paragraphStyle, .kern: 30])
addPropertiesToLabel(&label)

label.setAttributedText(attributedString, afterInheritingLabelAttributesAndConfiguringWithBlock: nil)

let updatedParagraphStyle = label.attributedText!.attribute(.paragraphStyle, at: 0, effectiveRange: nil) as! NSParagraphStyle
let updatedKern = label.attributedText!.attribute(.kern, at: 0, effectiveRange: nil) as! NSNumber

XCTAssertEqual(updatedParagraphStyle.lineSpacing, 21)
XCTAssertEqual(updatedParagraphStyle.minimumLineHeight, 31)
XCTAssertEqual(updatedParagraphStyle.maximumLineHeight, 41)
XCTAssertEqual(updatedKern.intValue, 31)
}

func testAttributedStringPropertiesUpdateWithBlock() {
let paragraphStyle = getParagraphStyle()
let attributedString = NSAttributedString(string: "Test string with properties", attributes: [.paragraphStyle: paragraphStyle, .kern: 30])
addPropertiesToLabel(&label)

let promise = expectation(description: "waiting for attributes to be set")

label.setAttributedText(attributedString) { mutableString -> NSMutableAttributedString in
defer {
promise.fulfill()
}
let range = NSRange(location: 0, length: mutableString.length)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 22
paragraphStyle.minimumLineHeight = 32
paragraphStyle.maximumLineHeight = 42

mutableString.addAttribute(.paragraphStyle, value: paragraphStyle, range: range)
mutableString.addAttribute(.kern, value: 32, range: range)
return mutableString
}

XCTWaiter().wait(for: [promise], timeout: 2)

let updatedParagraphStyle = label.attributedText!.attribute(.paragraphStyle, at: 0, effectiveRange: nil) as! NSParagraphStyle
let updatedKern = label.attributedText!.attribute(.kern, at: 0, effectiveRange: nil) as! NSNumber

XCTAssertEqual(updatedParagraphStyle.lineSpacing, 22)
XCTAssertEqual(updatedParagraphStyle.minimumLineHeight, 32)
XCTAssertEqual(updatedParagraphStyle.maximumLineHeight, 42)
XCTAssertEqual(updatedKern.intValue, 32)
}

private func addPropertiesToLabel(_ label: inout NantesLabel) {
label.lineSpacing = 21
label.minimumLineHeight = 31
label.maximumLineHeight = 41
label.kern = 31
}

private func getParagraphStyle() -> NSParagraphStyle {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 20
paragraphStyle.minimumLineHeight = 30
paragraphStyle.maximumLineHeight = 40
return paragraphStyle
}

private func addLink(_ link: NSAttributedString, to label: NantesLabel) {
let dataDetector = try! NSDataDetector(types: label.enabledTextCheckingTypes.rawValue)
let result = dataDetector.matches(in: link.string, options: .withTransparentBounds, range: NSRange(location: 0, length: link.length)).first
Expand Down
29 changes: 20 additions & 9 deletions Source/Classes/NantesLabel.swift
Expand Up @@ -309,18 +309,11 @@ public extension NSAttributedString.Key {
get {
return _attributedText
} set {
let attributes = NSAttributedString.attributes(from: self)
guard let updatedText = newValue?.mutableCopy() as? NSMutableAttributedString else {
return
}

updatedText.addAttributes(attributes, range: NSRange(location: 0, length: updatedText.length))

guard updatedText != _attributedText else {
guard newValue != _attributedText else {
return
}

_attributedText = updatedText
_attributedText = newValue
setNeedsFramesetter()
_accessibilityElements = nil
linkModels = []
Expand Down Expand Up @@ -542,6 +535,24 @@ public extension NSAttributedString.Key {

// MARK: - Public

/// Use this setter when you want to set attributes on NantesLabel before setting attributedText and have the properties get copied over
/// This will overwrite properties set on the attributedString passed in, if they're set on NantesLabel. Use `attributedText` if you want
/// to keep the properties inside attributedString
///
/// More info:
/// Check out the `testAttributedStringPropertiesUpdate` test and `testAttributedStringPropertiesUpdateWithBlock` and compare them against
/// `testAttributedStringPropertiesStay` for expected behavior against the functions
public func setAttributedText(_ attributedString: NSAttributedString, afterInheritingLabelAttributesAndConfiguringWithBlock block: ((NSMutableAttributedString) -> NSMutableAttributedString)?) {
var mutableAttributedString = NSMutableAttributedString(attributedString: attributedString)
mutableAttributedString.addAttributes(NSAttributedString.attributes(from: self), range: NSRange(location: 0, length: attributedString.length))

if let block = block {
mutableAttributedString = block(mutableAttributedString)
}

attributedText = mutableAttributedString
}

/// Adds a single link
open func addLink(_ link: NantesLabel.Link) {
addLinks([link])
Expand Down

0 comments on commit fac29a0

Please sign in to comment.