Fix compile errors: guard unwrap + ViewBuilder var mutation
Build iOS App / Build .ipa and publish source (push) Successful in 25s
Build iOS App / Build .ipa and publish source (push) Successful in 25s
- PRDetailView: guard let current = selectedTab ?? .overview fails because ?? already unwraps to non-optional. Use let current = selectedTab ?? .overview. - MarkdownText: var inCodeBlock inside ViewBuilder is not allowed. Precompute lines with their code-block state as a computed property on a private struct, then iterate via ForEach in the builder. Bump to 3.3.
This commit is contained in:
1 parent
dd0fc84238
commit
a9d408ee85
2 files changed
+58
-32
No files matched your search
@@ -195,8 +195,8 @@ struct PRDetailView: View {
|
|||||||
DragGesture(minimumDistance: 20)
|
DragGesture(minimumDistance: 20)
|
||||||
.onEnded { value in
|
.onEnded { value in
|
||||||
let tabs = DetailTab.allCases
|
let tabs = DetailTab.allCases
|
||||||
guard let current = selectedTab ?? .overview,
|
let current = selectedTab ?? .overview
|
||||||
let idx = tabs.firstIndex(of: current) else { return }
|
guard let idx = tabs.firstIndex(of: current) else { return }
|
||||||
if value.translation.width < -40, idx < tabs.count - 1 {
|
if value.translation.width < -40, idx < tabs.count - 1 {
|
||||||
withAnimation { selectedTab = tabs[idx + 1] }
|
withAnimation { selectedTab = tabs[idx + 1] }
|
||||||
} else if value.translation.width > 40, idx > 0 {
|
} else if value.translation.width > 40, idx > 0 {
|
||||||
|
|||||||
+56
-30
@@ -179,49 +179,75 @@ struct MarkdownText: View {
|
|||||||
self.markdown = markdown
|
self.markdown = markdown
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// One markdown source line with its render type resolved upfront, so the
|
||||||
|
/// code-block state doesn't have to be mutated inside the ViewBuilder.
|
||||||
|
private struct Line: Identifiable {
|
||||||
|
enum Kind { case code, heading, bullet, text, blank }
|
||||||
|
let id = UUID()
|
||||||
|
let kind: Kind
|
||||||
|
let text: String
|
||||||
|
let level: Int // heading level
|
||||||
|
}
|
||||||
|
|
||||||
|
private var lines: [Line] {
|
||||||
|
let raw = markdown.components(separatedBy: "\n")
|
||||||
|
var out: [Line] = []
|
||||||
|
var inCodeBlock = false
|
||||||
|
for line in raw {
|
||||||
|
if line.hasPrefix("```") {
|
||||||
|
inCodeBlock.toggle()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if inCodeBlock {
|
||||||
|
out.append(Line(kind: .code, text: line, level: 0))
|
||||||
|
} else if line.hasPrefix("# ") || line.hasPrefix("## ") || line.hasPrefix("### ") {
|
||||||
|
let level = line.prefix(while: { $0 == "#" }).count
|
||||||
|
out.append(Line(kind: .heading,
|
||||||
|
text: String(line.dropFirst(level)).trimmingCharacters(in: .whitespaces),
|
||||||
|
level: level))
|
||||||
|
} else if line.hasPrefix("- ") || line.hasPrefix("* ") {
|
||||||
|
out.append(Line(kind: .bullet,
|
||||||
|
text: String(line.dropFirst(2)),
|
||||||
|
level: 0))
|
||||||
|
} else if line.trimmingCharacters(in: .whitespaces).isEmpty {
|
||||||
|
out.append(Line(kind: .blank, text: "", level: 0))
|
||||||
|
} else {
|
||||||
|
out.append(Line(kind: .text, text: line, level: 0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 6) {
|
VStack(alignment: .leading, spacing: 6) {
|
||||||
let lines = markdown.components(separatedBy: "\n")
|
ForEach(lines) { line in
|
||||||
var inCodeBlock = false
|
switch line.kind {
|
||||||
ForEach(Array(lines.enumerated()), id: \.offset) { _, line in
|
case .code:
|
||||||
if line.hasPrefix("```") {
|
Text(line.text)
|
||||||
inCodeBlock.toggle()
|
|
||||||
} else if inCodeBlock {
|
|
||||||
Text(line)
|
|
||||||
.font(.caption.monospaced())
|
.font(.caption.monospaced())
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
.padding(4)
|
.padding(4)
|
||||||
.background(Color(.secondarySystemBackground))
|
.background(Color(.secondarySystemBackground))
|
||||||
} else if line.hasPrefix("# ") || line.hasPrefix("## ") || line.hasPrefix("### ") {
|
case .heading:
|
||||||
heading(line)
|
Text(line.text)
|
||||||
} else if line.hasPrefix("- ") || line.hasPrefix("* ") {
|
.font(line.level == 1 ? .title2.bold() : line.level == 2 ? .title3.bold() : .headline)
|
||||||
bullet(line)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
} else if line.trimmingCharacters(in: .whitespaces).isEmpty {
|
case .bullet:
|
||||||
|
HStack(alignment: .top, spacing: 6) {
|
||||||
|
Text("•")
|
||||||
|
inline(line.text)
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
case .blank:
|
||||||
Spacer(minLength: 4)
|
Spacer(minLength: 4)
|
||||||
} else {
|
case .text:
|
||||||
inline(line)
|
inline(line.text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func heading(_ line: String) -> some View {
|
|
||||||
let level = line.prefix(while: { $0 == "#" }).count
|
|
||||||
let text = line.dropFirst(level).trimmingCharacters(in: .whitespaces)
|
|
||||||
return Text(text)
|
|
||||||
.font(level == 1 ? .title2.bold() : level == 2 ? .title3.bold() : .headline)
|
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func bullet(_ line: String) -> some View {
|
|
||||||
HStack(alignment: .top, spacing: 6) {
|
|
||||||
Text("•")
|
|
||||||
inline(String(line.dropFirst(2)))
|
|
||||||
}
|
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func inline(_ text: String) -> some View {
|
private func inline(_ text: String) -> some View {
|
||||||
if let attributed = try? AttributedString(markdown: text) {
|
if let attributed = try? AttributedString(markdown: text) {
|
||||||
Text(attributed)
|
Text(attributed)
|
||||||
|
|||||||
Reference in new issue
Block a user