jellyflood/Swiftfin/Components/TruncatedTextView.swift

119 lines
4.5 KiB
Swift

//
// Swiftfin is subject to the terms of the Mozilla Public
// License, v2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
//
import SwiftUI
struct TruncatedTextView: View {
@State
private var truncated: Bool = false
@State
private var shrinkText: String
private var text: String
let font: UIFont
let lineLimit: Int
let seeMoreAction: () -> Void
private var moreLessText: String {
if !truncated {
return ""
} else {
return L10n.seeMore
}
}
init(
_ text: String,
lineLimit: Int,
font: UIFont = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.body),
seeMoreAction: @escaping () -> Void
) {
self.text = text
self.lineLimit = lineLimit
_shrinkText = State(wrappedValue: text)
self.font = font
self.seeMoreAction = seeMoreAction
}
var body: some View {
VStack(alignment: .center) {
Group {
Text(shrinkText)
.overlay {
if truncated {
LinearGradient(
stops: [
.init(color: .systemBackground.opacity(0), location: 0.5),
.init(color: .systemBackground.opacity(0.8), location: 0.7),
.init(color: .systemBackground, location: 1),
],
startPoint: .top,
endPoint: .bottom
)
}
}
}
.lineLimit(lineLimit)
.background {
// Render the limited text and measure its size
Text(text)
.lineLimit(lineLimit + 2)
.background {
GeometryReader { visibleTextGeometry in
Color.clear
.onAppear {
let size = CGSize(width: visibleTextGeometry.size.width, height: .greatestFiniteMagnitude)
let attributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.font: font]
var low = 0
var heigh = shrinkText.count
var mid = heigh
while (heigh - low) > 1 {
let attributedText = NSAttributedString(string: shrinkText, attributes: attributes)
let boundingRect = attributedText.boundingRect(
with: size,
options: NSStringDrawingOptions
.usesLineFragmentOrigin,
context: nil
)
if boundingRect.size.height > visibleTextGeometry.size.height {
truncated = true
heigh = mid
mid = (heigh + low) / 2
} else {
if mid == text.count {
break
} else {
low = mid
mid = (low + heigh) / 2
}
}
shrinkText = String(text.prefix(mid))
}
if truncated {
shrinkText = String(shrinkText.prefix(shrinkText.count - 2))
}
}
}
}
.hidden()
}
.font(Font(font))
if truncated {
Button {
seeMoreAction()
} label: {
Text(moreLessText)
}
}
}
}
}