diff --git a/README.md b/README.md index ba615e8..3b93e26 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,21 @@ func replaceCharacters(in range: NSRange, with attributedString: NSAttributedStr func replaceString(in range: NSRange, with attributedString: NSAttributedString) ``` +**Behavior** + +Changing NSTextView behaviors can be tricky, and often involve complex interactions with the whole system (NSLayoutManager, NSTextContainer, NSScrollView, etc). + +```swift +public var wrapsTextToHorizontalBounds: Bool +``` + +**Workarounds** + +```swift +// Fixes a widely-seen selection drawing artifact +func applySelectionDrawingWorkaround() +``` + ### Suggestions or Feedback We'd love to hear from you! Get in touch via [twitter](https://twitter.com/chimehq), an issue, or a pull request. diff --git a/TextViewPlus.xcodeproj/project.pbxproj b/TextViewPlus.xcodeproj/project.pbxproj index a7ee613..aca1b79 100644 --- a/TextViewPlus.xcodeproj/project.pbxproj +++ b/TextViewPlus.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + C908A15A24635F0600ADE4D9 /* NSTextView+Behavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = C908A15924635F0600ADE4D9 /* NSTextView+Behavior.swift */; }; C96AF30F244E1872004EB905 /* NSTextView+Workarounds.swift in Sources */ = {isa = PBXBuildFile; fileRef = C96AF30E244E1872004EB905 /* NSTextView+Workarounds.swift */; }; C9C7D7CD23BF6CCD00282686 /* TextViewPlus.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C9C7D7C323BF6CCC00282686 /* TextViewPlus.framework */; }; C9C7D7D223BF6CCD00282686 /* TextViewPlusTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9C7D7D123BF6CCD00282686 /* TextViewPlusTests.swift */; }; @@ -31,6 +32,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + C908A15924635F0600ADE4D9 /* NSTextView+Behavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTextView+Behavior.swift"; sourceTree = ""; }; C96AF30E244E1872004EB905 /* NSTextView+Workarounds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTextView+Workarounds.swift"; sourceTree = ""; }; C9C7D7C323BF6CCC00282686 /* TextViewPlus.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TextViewPlus.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C9C7D7C623BF6CCC00282686 /* TextViewPlus.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TextViewPlus.h; sourceTree = ""; }; @@ -98,6 +100,7 @@ C9C7D7E823BF6F6900282686 /* NSTextView+AttributedString.swift */, C9C7D7E723BF6E8400282686 /* TextViewPlus.xcconfig */, C96AF30E244E1872004EB905 /* NSTextView+Workarounds.swift */, + C908A15924635F0600ADE4D9 /* NSTextView+Behavior.swift */, ); path = TextViewPlus; sourceTree = ""; @@ -171,7 +174,7 @@ attributes = { LastSwiftUpdateCheck = 1130; LastUpgradeCheck = 1130; - ORGANIZATIONNAME = ChimeHq; + ORGANIZATIONNAME = "Chime Systems Inc"; TargetAttributes = { C9C7D7C223BF6CCC00282686 = { CreatedOnToolsVersion = 11.3; @@ -244,6 +247,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + C908A15A24635F0600ADE4D9 /* NSTextView+Behavior.swift in Sources */, C96AF30F244E1872004EB905 /* NSTextView+Workarounds.swift in Sources */, C9C7D7E223BF6D3800282686 /* NSTextView+Ranges.swift in Sources */, C9C7D7E323BF6D3800282686 /* NSTextView+Selection.swift in Sources */, diff --git a/TextViewPlus/NSTextView+Behavior.swift b/TextViewPlus/NSTextView+Behavior.swift new file mode 100644 index 0000000..ed0c275 --- /dev/null +++ b/TextViewPlus/NSTextView+Behavior.swift @@ -0,0 +1,61 @@ +// +// NSTextView+Behavior.swift +// TextViewPlus +// +// Created by Matt Massicotte on 2020-05-06. +// Copyright © 2020 Chime Systems Inc. All rights reserved. +// + +import Cocoa + +extension NSTextView { + private var maximumUsableWidth: CGFloat { + guard let scrollView = enclosingScrollView else { + return bounds.width + } + + let usableWidth = scrollView.contentSize.width - textContainerInset.width + + guard scrollView.rulersVisible, let rulerView = scrollView.verticalRulerView else { + return usableWidth + } + + return usableWidth - rulerView.requiredThickness + } + + // swiftlint:disable line_length + /// Controls the relative sizing behavior of the NSTextView and its NSTextContainer + /// + /// NSTextView scrolling behavior is tricky. Correct configuration of the enclosing + /// NSScrollView is required as well. But, this method does the basic setup, + /// as well as adjusting frame positions to account for any NSScrollView rulers. + /// + /// Check out: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/TextUILayer/Tasks/TextInScrollView.html + public var wrapsTextToHorizontalBounds: Bool { + get { + guard let container = textContainer else { + return false + } + + return container.widthTracksTextView + } + set { + textContainer?.widthTracksTextView = newValue + + let max = CGFloat.greatestFiniteMagnitude + + textContainer?.size = NSSize(width: max, height: max) + + // if we are turning on wrapping, our view could be the wrong size, + // so need to adjust it. Also, the textContainer's width could have + // been set to large, but adjusting the frame will fix that + // automatically + if newValue { + let newSize = NSSize(width: maximumUsableWidth, height: frame.height) + + self.frame = NSRect(origin: frame.origin, size: newSize) + } + } + } + // swiftlint:enable line_length +} diff --git a/TextViewPlus/NSTextView+Style.swift b/TextViewPlus/NSTextView+Style.swift index dab3e46..f8ea1a2 100644 --- a/TextViewPlus/NSTextView+Style.swift +++ b/TextViewPlus/NSTextView+Style.swift @@ -33,23 +33,4 @@ extension NSTextView { textStorage?.endEditing() } - - public var wrapsTextToHorizontalBounds: Bool { - get { - return textContainer?.widthTracksTextView ?? false - } - set { - guard let scrollView = enclosingScrollView else { - return - } - - let max = CGFloat.greatestFiniteMagnitude - let width = newValue ? scrollView.contentSize.width : max - let size = NSSize(width: width, height: max) - - textContainer?.containerSize = size - textContainer?.widthTracksTextView = newValue - isHorizontallyResizable = newValue == false - } - } } diff --git a/TextViewPlus/TextViewPlus.xcconfig b/TextViewPlus/TextViewPlus.xcconfig index 5092b01..cf7c7ca 100644 --- a/TextViewPlus/TextViewPlus.xcconfig +++ b/TextViewPlus/TextViewPlus.xcconfig @@ -3,7 +3,7 @@ // TextViewPlus // // Created by Matt Massicotte on 2020-01-03. -// Copyright © 2020 ChimeHq. All rights reserved. +// Copyright © 2020 Chime Systems Inc. All rights reserved. // // Configuration settings file format documentation can be found at: @@ -12,8 +12,8 @@ PRODUCT_NAME = TextViewPlus PRODUCT_BUNDLE_IDENTIFIER = com.chimehq.TextViewPlus PRODUCT_MODULE_NAME = TextViewPlus -CURRENT_PROJECT_VERSION = 4 -MARKETING_VERSION = 1.0.2 +CURRENT_PROJECT_VERSION = 5 +MARKETING_VERSION = 1.0.3 INFOPLIST_FILE = TextViewPlus/Info.plist FRAMEWORK_SEARCH_PATHS = $(PROJECT_DIR)/Carthage/Build/Mac $(PROJECT_DIR)/Carthage/Build/Mac/Static