Skip to content
3 changes: 2 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
25 changes: 18 additions & 7 deletions Sources/ExpandableText/ExpandableText+Modifiers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
70 changes: 59 additions & 11 deletions Sources/ExpandableText/ExpandableText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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 {
Expand All @@ -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.")
}