アプリ内に内包したテキストファイル(例えば利用規約など)を読み込んで表示させるまでに時間がかかる。15K bytesくらいのファイル。遅いといっても3秒〜5秒くらいなんですが、実際操作した時に表示させようとボタンなりを押して、たかだかテキストの表示で3秒〜5秒遅いというのは結構致命的ということで調査することに。
元々遅かったコードが以下。
struct EULAView: View {
@State var eula: String = ""
var body: some View {
VStack {
Text("利用規約").font(.headline).foregroundColor(.black)
ScrollView(content: {
VStack {
Text("\(self.eula)").multilineTextAlignment(.leading).foregroundColor(.black)
}
})
}
.onAppear(perform: {
Task.detached { @MainActor in
guard let fileUrl = Bundle.main.url(forResource: "eula_ja", withExtension: "txt") else {
fatalError("Not found license file.")
}
let eula = try String(contentsOf: fileUrl)
}
})
}
}
いろいろ調べてみると String(contentsOf: ) が遅いらしい。
調べて行き着いた方法は String ではなく Data を使うことに。ついでに VStack を LazyVStack に変更。
struct EULAView: View {
@State var eula: [Data] = []
var body: some View {
VStack {
Text("利用規約").font(.headline).foregroundColor(.black)
ScrollView(content: {
LazyVStack {
ForEach(0..<eula.count, id: \.self) { line in
if let text = String(data: eula[line], encoding: .utf8) {
Text("\(text)\n")
.multilineTextAlignment(.leading)
.foregroundColor(.black)
.frame(maxWidth: .infinity, alignment: .leading)
}
}
}
})
}
.onAppear(perform: {
Task.detached { @MainActor in
guard let fileUrl = Bundle.main.url(forResource: "eula_ja", withExtension: "txt") else {
fatalError("Not found license file.")
}
let fileData = try Data(contentsOf: fileUrl)
eula = fileData.split(separator: Data("\n".utf8))
}
})
}
}
上記のファイルから読み込んだ Data に対するデリミタ(上記では “\n” )による分割(Arrayに)するための Data.split は iOS16 以降から使えるようです。iOS15 以下のサポートも必要な場合は Data を Extension で拡張させて実装する感じで。
以下を見る限り iOS16 以降と言うわけでもなさそうに見えるけどXcode上だとビルド時に怒られる・・・。
split(separator:maxSplits:omittingEmptySubsequences:) (Apple公式)
extension Data {
func split(separator: Data, omittingEmptySubsequences: Bool = true) -> [Data] {
var current = startIndex
var chunks = [Data]()
while let range = self[current...].range(of: separator) {
if !omittingEmptySubsequences {
chunks.append(self[current..<range.lowerBound])
} else if range.lowerBound > current {
chunks.append(self[current..<range.lowerBound])
}
current = range.upperBound
}
if current < self.endIndex {
chunks.append(self[current...])
}
return chunks
}
}
上記の拡張部分は以下のサイトのものを利用させてもらいました。ありがとうございます。