fix(community): 커뮤니티 전체 아이템 말줄임과 폰트를 정렬, 텍스트 확장 동작을 개선한다

This commit is contained in:
Yu Sung
2026-03-04 17:30:28 +09:00
parent 9d6f0c648b
commit f0763d75c2
2 changed files with 199 additions and 21 deletions

View File

@@ -5,6 +5,7 @@
// Created by klaus on 2023/12/15.
//
import Foundation
import SwiftUI
import Kingfisher
import SDWebImageSwiftUI
@@ -20,7 +21,9 @@ struct CreatorCommunityAllItemView: View {
@State var isLike = false
@State var likeCount = 0
@State private var textHeight: CGFloat = .zero
@State private var isContentExpanded = false
@State private var isContentTruncated = false
@State private var contentTextWidth: CGFloat = 0
@StateObject var playManager = CreatorCommunityMediaPlayerManager.shared
@StateObject var contentPlayManager = ContentPlayManager.shared
@@ -55,11 +58,11 @@ struct CreatorCommunityAllItemView: View {
VStack(alignment: .leading, spacing: 3) {
Text(item.creatorNickname)
.appFont(size: 13.3, weight: .medium)
.appFont(size: 18, weight: .bold)
.foregroundColor(Color.grayee)
Text(item.relativeTimeText())
.appFont(size: 13.3, weight: .light)
.appFont(size: 14, weight: .regular)
.foregroundColor(Color.gray77)
}
.padding(.leading, 11)
@@ -73,22 +76,43 @@ struct CreatorCommunityAllItemView: View {
}
}
DetectableTextView(text: item.content, textSize: 13.3, font: Font.preMedium.rawValue)
.frame(
width: screenSize().width - 42,
height: textHeight
Group {
if isContentExpanded {
Text(linkedAttributedContent(from: item.content))
} else {
Text(item.content)
.lineLimit(3)
.truncationMode(.tail)
}
}
.appFont(size: 18, weight: .regular)
.foregroundColor(Color(hex: "B0BEC5"))
.fixedSize(horizontal: false, vertical: true)
.frame(maxWidth: .infinity, alignment: .leading)
.contentShape(Rectangle())
.background(
GeometryReader { proxy in
Color.clear
.onAppear {
updateContentWidth(proxy.size.width)
}
.onChange(of: proxy.size.width) { newWidth in
updateContentWidth(newWidth)
}
}
)
.onTapGesture {
guard isContentTruncated || isContentExpanded else { return }
isContentExpanded.toggle()
}
.onAppear {
self.textHeight = self.estimatedHeight(
for: item.content,
width: screenSize().width - 42
)
let width = contentTextWidth > 0 ? contentTextWidth : (screenSize().width - 42)
updateContentTruncationState(for: item.content, width: width)
}
.onChange(of: item.content) { newText in
self.textHeight = self.estimatedHeight(
for: newText,
width: screenSize().width - 42
)
isContentExpanded = false
let width = contentTextWidth > 0 ? contentTextWidth : (screenSize().width - 42)
updateContentTruncationState(for: newText, width: width)
}
if item.price <= 0 || item.existOrdered {
@@ -97,6 +121,7 @@ struct CreatorCommunityAllItemView: View {
WebImage(url: URL(string: imageUrl))
.resizable()
.scaledToFit()
.clipShape(RoundedRectangle(cornerRadius: 8, style: .continuous))
.clipped()
if let audioUrl = item.audioUrl {
@@ -149,16 +174,69 @@ struct CreatorCommunityAllItemView: View {
}
.padding(.horizontal, 8)
.padding(.vertical, 11)
.background(Color.gray22)
.cornerRadius(5.3)
.frame(maxWidth: .infinity)
.background(Color(hex: "263238"))
.cornerRadius(16)
.padding(.horizontal, 13.3)
}
private func updateContentWidth(_ width: CGFloat) {
guard width > 0 else { return }
contentTextWidth = width
updateContentTruncationState(for: item.content, width: width)
}
private func updateContentTruncationState(for text: String, width: CGFloat) {
let fullHeight = estimatedHeight(for: text, width: width)
let collapsedHeight = estimatedCollapsedHeight(lineLimit: 3)
isContentTruncated = fullHeight > (collapsedHeight + 0.5)
}
private func estimatedCollapsedHeight(lineLimit: Int) -> CGFloat {
let font = UIFont(name: Font.preRegular.rawValue, size: 18) ?? UIFont.systemFont(ofSize: 18, weight: .regular)
let lineCount = CGFloat(lineLimit)
return font.lineHeight * lineCount
}
private func estimatedHeight(for text: String, width: CGFloat) -> CGFloat {
let textView = UITextView(frame: CGRect(x: 0, y: 0, width: width, height: .greatestFiniteMagnitude))
textView.font = UIFont.systemFont(ofSize: 13.3)
textView.text = text
return textView.sizeThatFits(CGSize(width: width, height: .greatestFiniteMagnitude)).height
let attributes: [NSAttributedString.Key: Any] = [
.font: UIFont(name: Font.preRegular.rawValue, size: 18) ?? UIFont.systemFont(ofSize: 18, weight: .regular)
]
let rect = NSAttributedString(string: text, attributes: attributes)
.boundingRect(
with: CGSize(width: width, height: .greatestFiniteMagnitude),
options: [.usesLineFragmentOrigin, .usesFontLeading],
context: nil
)
return ceil(rect.height)
}
private func linkedAttributedContent(from text: String) -> AttributedString {
var attributedText = AttributedString(text)
guard
let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
else {
return attributedText
}
let nsText = text as NSString
let matches = detector.matches(in: text, options: [], range: NSRange(location: 0, length: nsText.length))
for match in matches {
guard
let url = match.url,
let range = Range(match.range, in: attributedText)
else {
continue
}
attributedText[range].link = url
}
return attributedText
}
}