Zebra-Streifen für Tabellenansichten (found.md #8)
TableZebra-Helper (AppKit NSColor.alternatingContentBackgroundColors)
angewendet auf: LAN-Scanner-Geräte/Rohfelder/Port-Scan, Experte-
Eintragsliste + "Weitere Parameter"-Grid, Bekannte Router + Interface-
Liste (Verbinden), Sicherungsliste, Review/Apply-Log, Übersicht-
Knotendetails.
Root Cause für "keine Änderung erkennbar": .formStyle(.grouped) malt
einen eigenen blickdichten Hintergrund über Section-Zeilen, der
.listRowBackground verdeckt — .scrollContentBackground(.hidden) ergänzt.
Live bestätigt ("passt").
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,18 @@
|
||||
import SwiftUI
|
||||
import AppKit
|
||||
|
||||
/// Alternating row backgrounds for table-like `Form`/`Section`/`ForEach` lists (LAN-Scanner
|
||||
/// device rows, Experte-tab item lists) — per explicit readability request. Uses AppKit's own
|
||||
/// `NSColor.alternatingContentBackgroundColors` (the same pair `NSTableView` uses for zebra
|
||||
/// striping) rather than a hand-picked gray, so it already matches the system's row-striping
|
||||
/// color and adapts to Light/Dark Mode for free.
|
||||
enum TableZebra {
|
||||
static func color(for index: Int) -> Color {
|
||||
let pair = NSColor.alternatingContentBackgroundColors
|
||||
guard pair.count > 1 else { return .clear }
|
||||
return Color(nsColor: pair[index % 2])
|
||||
}
|
||||
}
|
||||
|
||||
/// Central catalog for the "Settings" window (⌘,) — every value here is read/written through
|
||||
/// `@AppStorage`/`UserDefaults.standard` under the raw string keys named on each property below,
|
||||
|
||||
@@ -95,7 +95,7 @@ struct BackupListView: View {
|
||||
} else {
|
||||
Form {
|
||||
Section(L10n.t("Sicherungen", appLanguage)) {
|
||||
ForEach(viewModel.backups) { backup in
|
||||
ForEach(Array(viewModel.backups.enumerated()), id: \.element.id) { index, backup in
|
||||
HStack {
|
||||
VStack(alignment: .leading) {
|
||||
Text(backup.host).bold()
|
||||
@@ -116,10 +116,12 @@ struct BackupListView: View {
|
||||
.disabled(connectionService.credentials == nil || viewModel.isRestoring)
|
||||
.help(L10n.t("Diese Sicherung auf den verbundenen Router zurückspielen — nur für exakt dasselbe Routermodell.", appLanguage))
|
||||
}
|
||||
.listRowBackground(TableZebra.color(for: index))
|
||||
}
|
||||
}
|
||||
}
|
||||
.formStyle(.grouped)
|
||||
.scrollContentBackground(.hidden)
|
||||
}
|
||||
}
|
||||
.safeAreaInset(edge: .bottom) {
|
||||
|
||||
@@ -59,7 +59,7 @@ struct DevicesView: View {
|
||||
Text(L10n.t("Keine Geräte", appLanguage)).appFont(.caption).foregroundStyle(.secondary)
|
||||
} else {
|
||||
DeviceColumnHeader(appLanguage: appLanguage)
|
||||
ForEach(group.devices) { device in
|
||||
ForEach(Array(group.devices.enumerated()), id: \.element.id) { index, device in
|
||||
HStack(spacing: 4) {
|
||||
DeviceRow(device: device, appLanguage: appLanguage, theme: colorTheme)
|
||||
Spacer()
|
||||
@@ -78,6 +78,7 @@ struct DevicesView: View {
|
||||
.fixedSize()
|
||||
.help(L10n.t("Aktionen", appLanguage))
|
||||
}
|
||||
.listRowBackground(TableZebra.color(for: index))
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
@@ -98,6 +99,12 @@ struct DevicesView: View {
|
||||
}
|
||||
}
|
||||
.formStyle(.grouped)
|
||||
// `.formStyle(.grouped)` paints its own opaque background over each
|
||||
// Section's rows — `.listRowBackground` (Zebra-Streifen below) was applied
|
||||
// correctly but rendered invisible underneath it. Confirmed live ("keine
|
||||
// Änderungen erkennbar"): this is the documented fix, not a bug in the color
|
||||
// computation itself.
|
||||
.scrollContentBackground(.hidden)
|
||||
}
|
||||
}
|
||||
.overlay(alignment: .bottom) {
|
||||
@@ -480,12 +487,14 @@ private struct RawFieldsSheet: View {
|
||||
.foregroundStyle(.secondary)
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
ForEach(device.rawFields, id: \.key) { pair in
|
||||
ForEach(Array(device.rawFields.enumerated()), id: \.element.key) { index, pair in
|
||||
HStack(alignment: .top) {
|
||||
Text(pair.key).appFont(.caption, design: .monospaced).foregroundStyle(.secondary)
|
||||
Spacer()
|
||||
Text(pair.value).appFont(.caption, design: .monospaced).multilineTextAlignment(.trailing)
|
||||
}
|
||||
.padding(.vertical, 2)
|
||||
.background(TableZebra.color(for: index))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -577,6 +586,7 @@ private struct PortScanResultSheet: View {
|
||||
ForEach(Array(result.entries.enumerated()), id: \.element.id) { index, entry in
|
||||
if index > 0 { Divider() }
|
||||
PortScanRow(entry: entry, appLanguage: appLanguage, theme: theme)
|
||||
.background(TableZebra.color(for: index))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ struct ExpertMenuDetailView: View {
|
||||
// `.sheet(item:)` dismissal already forces a remount of this view on its own;
|
||||
// delete's confirmationDialog doesn't tear this view down at all, so nothing
|
||||
// was forcing SwiftUI to re-diff the Section in place.
|
||||
ForEach(viewModel.items) { item in
|
||||
ForEach(Array(viewModel.items.enumerated()), id: \.element.id) { index, item in
|
||||
HStack {
|
||||
Button {
|
||||
viewModel.startEditing(item)
|
||||
@@ -61,6 +61,7 @@ struct ExpertMenuDetailView: View {
|
||||
}
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.listRowBackground(TableZebra.color(for: index))
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
@@ -75,6 +76,9 @@ struct ExpertMenuDetailView: View {
|
||||
.id(viewModel.items.count)
|
||||
}
|
||||
.formStyle(.grouped)
|
||||
// See DevicesView's identical fix: `.formStyle(.grouped)` paints an opaque background
|
||||
// over each Section's rows, hiding `.listRowBackground` (Zebra-Streifen) underneath it.
|
||||
.scrollContentBackground(.hidden)
|
||||
.navigationTitle(LocalizedStringKey(L10n.t(schema.displayName, appLanguage)))
|
||||
.task(id: schema.id) { await viewModel.reloadItems() }
|
||||
.sheet(item: $viewModel.editingItem) { item in
|
||||
@@ -171,7 +175,7 @@ struct ExpertItemEditView: View {
|
||||
extraColumnHeader("Parameter")
|
||||
extraColumnHeader("Wert")
|
||||
}
|
||||
ForEach(Array(stride(from: 0, to: viewModel.extraFields.count, by: 2)), id: \.self) { start in
|
||||
ForEach(Array(stride(from: 0, to: viewModel.extraFields.count, by: 2).enumerated()), id: \.element) { rowIndex, start in
|
||||
GridRow {
|
||||
extraKeyField(at: start)
|
||||
extraValueField(at: start)
|
||||
@@ -188,6 +192,7 @@ struct ExpertItemEditView: View {
|
||||
Color.clear
|
||||
}
|
||||
}
|
||||
.background(TableZebra.color(for: rowIndex))
|
||||
Divider()
|
||||
.gridCellColumns(5)
|
||||
}
|
||||
|
||||
@@ -833,12 +833,14 @@ private struct NodeDetailView: View {
|
||||
if !node.detail.isEmpty {
|
||||
Divider()
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
ForEach(node.detail, id: \.key) { pair in
|
||||
ForEach(Array(node.detail.enumerated()), id: \.element.key) { index, pair in
|
||||
HStack(alignment: .top) {
|
||||
Text(pair.key).appFont(.caption, design: .monospaced).foregroundStyle(.secondary)
|
||||
Spacer()
|
||||
Text(pair.value).appFont(.caption, design: .monospaced).multilineTextAlignment(.trailing)
|
||||
}
|
||||
.padding(.vertical, 2)
|
||||
.background(TableZebra.color(for: index))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -847,7 +849,7 @@ private struct NodeDetailView: View {
|
||||
if !connected.isEmpty {
|
||||
Divider()
|
||||
Text(L10n.t("Verbindungen", appLanguage)).appFont(.subheadline, bold: true)
|
||||
ForEach(connected) { edge in
|
||||
ForEach(Array(connected.enumerated()), id: \.element.id) { index, edge in
|
||||
let otherID = edge.from == node.id ? edge.to : edge.from
|
||||
let otherTitle = graph.nodes.first(where: { $0.id == otherID })?.title ?? otherID
|
||||
Button {
|
||||
@@ -862,6 +864,9 @@ private struct NodeDetailView: View {
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.help(L10n.t(OverviewStyle.explanation(for: edge.kind), appLanguage))
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.vertical, 2)
|
||||
.background(TableZebra.color(for: index))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ struct ConnectView: View {
|
||||
onDelete: { viewModel.removeSavedRouter(router.id) }
|
||||
)
|
||||
.padding(.vertical, 6)
|
||||
.background(TableZebra.color(for: index))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -351,7 +352,7 @@ struct ConnectView: View {
|
||||
}
|
||||
}
|
||||
Section("Interfaces") {
|
||||
ForEach(connectionService.interfaces) { interface in
|
||||
ForEach(Array(connectionService.interfaces.enumerated()), id: \.element.id) { index, interface in
|
||||
HStack {
|
||||
InterfaceActivityDot(
|
||||
running: interface.running,
|
||||
@@ -362,10 +363,12 @@ struct ConnectView: View {
|
||||
Text(interface.type).appFont(.caption).foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
.listRowBackground(TableZebra.color(for: index))
|
||||
}
|
||||
}
|
||||
}
|
||||
.formStyle(.grouped)
|
||||
.scrollContentBackground(.hidden)
|
||||
} else {
|
||||
ContentUnavailableView(
|
||||
LocalizedStringKey(L10n.t("Nicht verbunden", appLanguage)),
|
||||
|
||||
@@ -10,7 +10,7 @@ struct ReviewApplyView: View {
|
||||
var body: some View {
|
||||
Form {
|
||||
Section(L10n.t("Geplante Änderungen", appLanguage)) {
|
||||
ForEach(viewModel.plannedCommands) { command in
|
||||
ForEach(Array(viewModel.plannedCommands.enumerated()), id: \.element.id) { index, command in
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(command.summary)
|
||||
if showCliDetails {
|
||||
@@ -19,6 +19,7 @@ struct ReviewApplyView: View {
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
.listRowBackground(TableZebra.color(for: index))
|
||||
}
|
||||
Toggle(L10n.t("Details anzeigen (RouterOS-Befehle)", appLanguage), isOn: $showCliDetails)
|
||||
}
|
||||
@@ -31,8 +32,9 @@ struct ReviewApplyView: View {
|
||||
|
||||
if !viewModel.applyLog.isEmpty {
|
||||
Section(L10n.t("Ablauf", appLanguage)) {
|
||||
ForEach(viewModel.applyLog, id: \.self) { line in
|
||||
ForEach(Array(viewModel.applyLog.enumerated()), id: \.offset) { index, line in
|
||||
Text(line).appFont(.caption)
|
||||
.listRowBackground(TableZebra.color(for: index))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,6 +72,7 @@ struct ReviewApplyView: View {
|
||||
}
|
||||
}
|
||||
.formStyle(.grouped)
|
||||
.scrollContentBackground(.hidden)
|
||||
.navigationTitle(LocalizedStringKey(L10n.t("Übersicht & Anwenden", appLanguage)))
|
||||
.alert(
|
||||
L10n.t("Anwenden fehlgeschlagen", appLanguage),
|
||||
|
||||
Reference in New Issue
Block a user