Bilder ohne Warten — und der Beweis, dass der Mac die Leiste selbst zeichnet

Der Schriftzug hing weiter nicht an der Animation, und der Zwischenspeicher war
nicht schuld: er lag im Actor, und ein `await` kostet immer mindestens einen
Durchlauf. Das neue Bild erschien deshalb im ersten Bild ohne Schriftzug und im
zweiten mit — er sprang also getrennt herein, obwohl er längst da war.

Der Speicher liegt jetzt außerhalb des Actors und lässt sich beim Zeichnen
lesen. Das gilt für alle Bilder: auch eine zurückscrollende Kachel blitzt nicht
mehr kurz grau auf.

Zur Seitenleiste auf dem Mac, gemessen statt vermutet: `automatic` löst sich
laut Apples eigener Beschreibung auf iOS zu `overlap` auf (die Leiste schwebt
über dem Inhalt) und auf macOS zu `tile` (sie schiebt ihn zur Seite und klebt
am Fensterrand). Der Griff dorthin sitzt richtig — derselbe Weg hat die Leiste
auf Kommando ganz ausgeblendet —, aber `overlap` wird auf dem Mac
stillschweigend verworfen. Die Bitte bleibt trotzdem stehen; sie kostet nichts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-09-13 17:40:46 +02:00
co-authored by Claude Opus 5
parent e5e01838f7
commit e7de8a9055
7 changed files with 183 additions and 19 deletions
+11
View File
@@ -170,6 +170,13 @@ struct RootView: View {
/// ausfahrbares Blatt, eine geteilte Ansicht und eine Reihe oben. Sie
/// konnten zusammen weniger als das eine Bauteil und mussten dreimal
/// gepflegt werden.
/// Läuft diese App gerade auf einem Mac?
///
/// Nicht über `#if` zu haben: „Designed for iPad" **ist** der iOS-Bau,
/// unverändert, nur auf anderer Hardware. Erst zur Laufzeit ist der
/// Unterschied sichtbar.
static let isOnMac = ProcessInfo.processInfo.isMacCatalystApp
private var library: some View {
TabView(selection: $selection) {
Tab("sidebar.home", systemImage: "house", value: SidebarDestination.home) {
@@ -241,6 +248,10 @@ struct RootView: View {
}
.tabViewStyle(.sidebarAdaptable)
.tabViewCustomization($customization)
// Auf dem Mac würde die Leiste sonst den Inhalt zur Seite schieben
// und selbst am Fensterrand kleben. Auf dem iPad tut das System es
// ohnehin so; der Aufruf schadet dort nicht.
.background { SidebarOverlapRequest().frame(width: 0, height: 0) }
// Das Konto sitzt unten in der Seitenleiste, nicht als Eintrag
// zwischen den Mediatheken. Dort sucht man es.
.tabViewSidebarBottomBar {
@@ -0,0 +1,68 @@
#if os(iOS)
import SwiftUI
import UIKit
/// Bittet die Seitenleiste, den Inhalt zu **überlagern** statt ihn zu verdrängen.
///
/// `UITabBarControllerSidebar.preferredLayout` steht ab Werk auf `automatic`,
/// und das löst sich laut Apples eigener Beschreibung je nach Gerät anders auf:
/// auf iOS zu `overlap` — die Leiste schwebt als eigene Fläche über dem Inhalt —
/// und auf macOS zu `tile`, wo sie den Inhalt zur Seite schiebt und selbst am
/// Fensterrand klebt. Das ist der Unterschied, den man zwischen iPad und Mac
/// sieht, und er kommt nicht aus dieser App.
///
/// **Unter Mac Catalyst gemessen: es hilft dort nicht.** Der Griff sitzt
/// richtig — zum Beweis hat derselbe Weg die Leiste auf Kommando ganz
/// ausgeblendet —, aber `overlap` wird auf dem Mac stillschweigend verworfen
/// und es bleibt bei `tile`. Der Aufruf bleibt trotzdem stehen: er ist die
/// richtige Bitte, er kostet nichts, und sollte „Designed for iPad" sich näher
/// am iPhone-Bau verhalten als Catalyst, greift er dort.
///
/// SwiftUI reicht die Eigenschaft nicht durch, also wird der zuständige
/// `UITabBarController` gesucht. Gesucht wird vom eigenen Platz aus nach oben
/// bis zur Wurzel und von dort nach unten — die Leiste liegt in der
/// SwiftUI-Sicht *über* uns, nicht unter uns, und die reine Elternkette führt
/// deshalb an ihr vorbei.
struct SidebarOverlapRequest: UIViewControllerRepresentable {
func makeUIViewController(context: Context) -> UIViewController {
Probe()
}
func updateUIViewController(_ controller: UIViewController, context: Context) {}
/// Ein Controller, der nichts zeigt und nur da ist, um im Baum zu stehen.
private final class Probe: UIViewController {
override func didMove(toParent parent: UIViewController?) {
super.didMove(toParent: parent)
apply()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Ein zweites Mal: beim ersten Anhängen steht der
// Tab-Controller noch nicht zwingend im Baum.
apply()
}
private func apply() {
guard let controller = Self.tabBarController(from: self) else { return }
guard controller.sidebar.preferredLayout != .overlap else { return }
controller.sidebar.preferredLayout = .overlap
}
private static func tabBarController(from start: UIViewController) -> UITabBarController? {
var root = start
while let parent = root.parent { root = parent }
return search(root)
}
private static func search(_ controller: UIViewController) -> UITabBarController? {
if let found = controller as? UITabBarController { return found }
for child in controller.children {
if let found = search(child) { return found }
}
return nil
}
}
}
#endif