diff --git a/Package.swift b/Package.swift index 4e0d394..f9173f9 100644 --- a/Package.swift +++ b/Package.swift @@ -6,7 +6,8 @@ import PackageDescription let package = Package( name: "ExpandableText", platforms: [ - .iOS(.v13) + .iOS(.v14), + .macOS(.v12) ], products: [ // Products define the executables and libraries a package produces, and make them visible to other packages. diff --git a/README.md b/README.md index 7bace8d..f2b4f65 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ [![build](https://github.com/n3d1117/ExpandableText/actions/workflows/build.yml/badge.svg)](https://github.com/n3d1117/ExpandableText/actions/workflows/build.yml) [![swift-version](https://img.shields.io/badge/swift-5.7-orange.svg)](https://github.com/apple/swift/) [![ios-version](https://img.shields.io/badge/ios-13.0+-brightgreen.svg)](https://github.com/apple/ios/) +[![macos-version](https://img.shields.io/badge/macos-12.0+-brightgreen.svg)](https://github.com/apple/macos/) [![xcode-version](https://img.shields.io/badge/xcode-14.2-blue)](https://developer.apple.com/xcode/) [![license](https://img.shields.io/badge/license-The%20Unlicense-yellow.svg)](LICENSE) diff --git a/Sources/ExpandableText/ExpandableText+Modifiers.swift b/Sources/ExpandableText/ExpandableText+Modifiers.swift index d7cb4ee..00ef413 100644 --- a/Sources/ExpandableText/ExpandableText+Modifiers.swift +++ b/Sources/ExpandableText/ExpandableText+Modifiers.swift @@ -54,25 +54,36 @@ public extension ExpandableText { return copy } + /** + Sets the text to use for the "show less" button in the `ExpandableText` instance. + - Parameter moreText: The text to use for the "show less" button. Defaults to `less` + - Returns: A new `ExpandableText` instance with the specified "show more" button text applied. + */ + func lessButtonText(_ lessText: String) -> Self { + var copy = self + copy.lessButtonText = lessText + return copy + } + /** Sets the font to use for the "show more" button in the `ExpandableText` instance. - Parameter font: The font to use for the "show more" button. Defaults to the same font as the text - Returns: A new `ExpandableText` instance with the specified "show more" button font applied. */ - func moreButtonFont(_ font: Font) -> Self { + func buttonFont(_ font: Font) -> Self { var copy = self - copy.moreButtonFont = font + copy.buttonFont = font return copy } /** - Sets the color to use for the "show more" button in the `ExpandableText` instance. - - Parameter color: The color to use for the "show more" button. Defaults to `accentColor` - - Returns: A new `ExpandableText` instance with the specified "show more" button color applied. + Sets the color to use for the "show less/more" buttons in the `ExpandableText` instance. + - Parameter color: The color to use for the "show less/more" buttons. Defaults to `accentColor` + - Returns: A new `ExpandableText` instance with the specified "show less/more" buttons color applied. */ - func moreButtonColor(_ color: Color) -> Self { + func buttonColor(_ color: Color) -> Self { var copy = self - copy.moreButtonColor = color + copy.buttonColor = color return copy } diff --git a/Sources/ExpandableText/ExpandableText.swift b/Sources/ExpandableText/ExpandableText.swift index 43cdff0..439533c 100644 --- a/Sources/ExpandableText/ExpandableText.swift +++ b/Sources/ExpandableText/ExpandableText.swift @@ -27,9 +27,9 @@ ExpandableText("Lorem ipsum dolor sit amet, consectetur adipiscing elit...") */ public struct ExpandableText: View { - @State private var isExpanded: Bool = false + @State internal var isExpanded: Bool = false @State private var isTruncated: Bool = false - + @State private var showLessButton: Bool = false @State private var intrinsicSize: CGSize = .zero @State private var truncatedSize: CGSize = .zero @State private var moreTextSize: CGSize = .zero @@ -39,8 +39,9 @@ public struct ExpandableText: View { internal var color: Color = .primary internal var lineLimit: Int = 3 internal var moreButtonText: String = "more" - internal var moreButtonFont: Font? - internal var moreButtonColor: Color = .accentColor + internal var lessButtonText: String = "less" + internal var buttonFont: Font? + internal var buttonColor: Color = .accentColor internal var expandAnimation: Animation = .default internal var collapseEnabled: Bool = false internal var trimMultipleNewlinesWhenTruncated: Bool = true @@ -56,6 +57,11 @@ public struct ExpandableText: View { public var body: some View { content + .onChange(of: text) { _ in + if isExpanded { + isExpanded = false + } + } .lineLimit(isExpanded ? nil : lineLimit) .applyingTruncationMask(size: moreTextSize, enabled: shouldShowMoreButton) .readSize { size in @@ -74,28 +80,66 @@ public struct ExpandableText: View { ) .background( Text(moreButtonText) - .font(moreButtonFont ?? font) + .font(buttonFont ?? font) + .hidden() + .readSize { moreTextSize = $0 } + ) + .background( + Text(lessButtonText) + .font(buttonFont ?? font) .hidden() .readSize { moreTextSize = $0 } ) .contentShape(Rectangle()) .onTapGesture { - if (isExpanded && collapseEnabled) || - shouldShowMoreButton { - withAnimation(expandAnimation) { isExpanded.toggle() } + if (isExpanded && collapseEnabled) || shouldShowMoreButton { + withAnimation(expandAnimation) { + isExpanded.toggle() + showLessButton = false + } } } .modifier(OverlayAdapter(alignment: .trailingLastTextBaseline, view: { if shouldShowMoreButton { Button { - withAnimation(expandAnimation) { isExpanded.toggle() } + withAnimation(expandAnimation) { + isExpanded.toggle() + showLessButton = false + } } label: { Text(moreButtonText) - .font(moreButtonFont ?? font) - .foregroundColor(moreButtonColor) + .font(buttonFont ?? font) + .foregroundColor(buttonColor) + } + .buttonStyle(.plain) + .animation(.smooth, value: shouldShowMoreButton) + } else if (isExpanded && !isTruncated) { + VStack { + if showLessButton { + Button { + withAnimation(expandAnimation) { + showLessButton = false + isExpanded.toggle() + } + } label: { + Text(lessButtonText) + .font(buttonFont ?? font) + .foregroundColor(buttonColor) + } + .buttonStyle(.plain) + } + } + .animation(.smooth, value: showLessButton) + .animation(.smooth, value: shouldShowMoreButton) + .onAppear { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) { + // Show the button after a delay of 0.25 seconds + showLessButton = true + } } } })) + .animation(.smooth, value: shouldShowMoreButton) } private var content: some View { @@ -117,3 +161,7 @@ public struct ExpandableText: View { text.replacingOccurrences(of: #"\n\s*\n"#, with: "\n", options: .regularExpression) } } + +#Preview { + ExpandableText("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.") +}