この記事はSwiftを勉強する過程で書き溜めたものを、もったいないかも精神で投稿しています。上手くいかなかったり、他にスマートな方法があるかもしれません。
はじめに
表示するテキストが指定した行数を超えると、途中で省略して「続きを表示」ボタンを出します。SNS系のアプリでよく見るやつです。
参考元
SwiftUIでTruncated Text|TAAT
SwiftUI: How to make see more see less style butto
参考元様との違いは「続きを表示」がテキスト中に表示されるか、次の行に表示するかが異なります。
今回は「X」のまんまを実装したかったので、シンプルに後者で実装しました。
確認した環境
Xcode 16.0
iOS 18.0
考え方
テキストの省略表示自体はlineLimit
モディファイアで実現できます。「続きを表示」を表示するか否かをlineLimit
を設定しないパターンと比較して制御します。
具体的な流れは、下記のとおりです。
-
lineLimit
を使って表示する行数を指定 - 制限せずにテキストを表示した場合の高さを、1.の
background
に非表示で仕込む - 1.と2.を比較して相違がある場合、テキストが省略されていると判断して「続きを表示」ボタンを表示
コード
struct ExpandableTextView: View {
@State private var isExpanded = false // テキストが展開されているかどうかの状態
@State private var isTruncated = false // テキストが制限行数を超えているかの状態
let text: String
let lineLimit: Int // 制限行数
var body: some View {
VStack(alignment: .leading) {
// テキスト表示部分
Text(text)
.lineLimit(isExpanded ? nil : lineLimit)
.background(
// 「続きを読む」用にテキストを制限パターンと無制限パターンを非表示で生成して高さを比較する
Text(text) // A.指定行数制限されるテキストを設置して、サイズを計測
.lineLimit(lineLimit)
.background(GeometryReader { limitTextGeometry in
ZStack {
Text(text) // B.全表示されるテキストを設置し、サイズを計測
.background(GeometryReader { fullTextGeometry in
Color.clear.onAppear {
// BとAを比較してB(全表示)が大きい場合は省略されていると判定
isTruncated = fullTextGeometry.size.height > limitTextGeometry.size.height
}
})
}
.frame(height: .greatestFiniteMagnitude) // 子が親の高さに依存しない
})
.hidden() // 計算用に使ったテキストは非表示にする
)
// 「続きを表示」または「閉じる」ボタン
if isTruncated { // テキストが制限行数を超えている場合にのみ表示
Button(action: {
isExpanded.toggle()
}) {
Text(isExpanded ? "閉じる" : "続きを表示")
.foregroundColor(.blue)
}
}
}
}
}