USB-Meter / Documentation / Issues / 002_2026-03-24_18-45_designed-for-ipad-nav-feedback-loop.md
Newer Older
94 lines | 6.029kb
Bogdan Timofte authored 2 weeks ago
1
# Issue: UIObservationTrackingFeedbackLoop on "Designed for iPad" macOS with NavigationBarContentView
2

            
3
## Description
4
- Aplicația USB Meter (SwiftUI "Designed for iPad") se bloaca instant la deschiderea `MeterView` pe macOS Apple Silicon, cu eroare repetata `UIObservationTrackingFeedbackLoopDetected` in `NavigationBarContentView.updateProperties`.
5
- Freezeul apare **inainte** ca datele BLE să ajunga (instant la deschidere), diferit de problema Catalyst (care era blocata pe navigare).
6
- Condiții: macOS Apple Silicon cu "Designed for iPad" mode (UIKit hosted SwiftUI). Pe Catalyst, NU apare (AppKit toolbar remplace NavigationBarContentView).
7

            
8
## Impact
9
- Imposibil de deschis meter view pe "Designed for iPad" macOS
10
- Aplicatia devine complet neresponsiva, CPU la 100%
11
- Error stack: `NavigationBarContentView` intra in ciclu infinit de observare SwiftUI (sa se schimbe ceva constant)
12

            
13
## Root Cause Analysis
14

            
15
### Ipoteza initiala (INCORECTA): Update frequency pe model
16
- Prim diagnostic: "poate BLE packet-uri (~1Hz) cu 15+ @Published pe Meter triggerez body re-evaluate, care triggerez NavigationBar update, care cicleaza la infinit"
17
- **CONCLUZIUNE incorecta**: problema nu este frecventa datelor; eroarea apare instant, inainte ca Bluetooth sa se conecteze
18

            
19
### Adevarat root cause (GASIT): UIKit NavigationBarContentView bug
20
- NavigationBarContentView (UIKit hosted component) are implement basata pe @Observable tracking
21
- Cand SwiftUI seteaza `.navigationBarTitle()` + `.toolbar {}` pe o view push-uit in split NavigationView, UIKit intra in infinite feedback loop pe observation changes
22
- Bug specific la "Designed for iPad" pe macOS; pe Catalyst NU apare (AppKit toolbar inlocuieste NavigationBarContentView)
23
- Eroarea apare **inainte** ca bind pe meter data; doar configurarea vue modifiers triggerez loop-ul
24

            
25
## Investigatie si attempts
26

            
27
### 1. Model-layer deduplication (PARTIAL, nu era fix principal)
28
Implemented:
29
- `BluetoothSerial.swift`: RSSI averaging (sliding window 3 = ~9s), singura @Published ramasa
30
- `Meter.swift`: Eliminat @Published de la ~20 proprietati per-packet (voltage, current, power, temps, etc.). Adaugat `setIfChanged()` helper cu dedup. Singur `objectWillChange.send()` la final de `parseData()` doar daca ceva s-a schimbat. Guard pe `operationalState` oscillation
31
- `Measurements.swift`: `objectWillChange.send()` doar cand punct e adaugat (nu pe accumulation)
32
- **Rezultat**: Zero improvement pe problema Designed-for-iPad. Insa sunt optimizari bune pentru defense-in-depth.
33

            
34
### 2. Toolbar migration (DEPRECATED API FIX, nu era problema)
35
- Inlocuit `.navigationBarItems(trailing:)` cu `.toolbar { ToolbarItemGroup }` in MeterView
36
- Adaugat @State buffers (`navBarTitle`, `navBarShowRSSI`, `navBarRSSI`) ca shield
37
- **Rezultat**: Zero improvement. Nu era problema API veche; problema era conceptuala (UIKit nav bar pe Mac).
38

            
39
### 3. UIKit NavigationBarContentView bypass (SOLUTIE FINALA)
40
- Runtime detection: `ProcessInfo.processInfo.isiOSAppOnMac` (true doar pe "Designed for iPad" pe Mac)
41
- **MeterView.swift**:
42
  - Adaugat `static let isMacIPadApp: Bool = ProcessInfo.processInfo.isiOSAppOnMac`
43
  - Body rewritten: VStack cu conditional `macNavigationHeader` (custom back button via `dismiss()`, title, RSSI, settings)
44
  - `.navigationBarHidden(true)` cand `isMacIPadApp || landscape`
45
  - Creat `IOSOnlyNavBar` ViewModifier (@ViewBuilder if/else): applies `.navigationBarTitle()` + `.toolbar {}` DOAR cand `!isMacIPadApp`, passeaza content nemodificat pe Mac
46
- **MeterSettingsView.swift**: Acelasi pattern
47
  - Custom `macSettingsHeader` cu back button, title, RSSI
48
  - `IOSOnlySettingsNavBar` ViewModifier pentru conditional nav bar modifiers
49
- Pe real iPad: toti modifiers se aplica normal (nav bar standard UIKit)
50

            
51
## Solutie finala aplicata
52

            
53
### Fișiere modificate
54
1. **MeterView.swift** (728 → 765 lines)
55
   - Adaugat `@Environment(\.dismiss)` si `isMacIPadApp` static const
56
   - Rewritten body cu conditional `macNavigationHeader` (HStack cu back button, title, RSSI, settings NavigationLink)
57
   - Adaugat `IOSOnlyNavBar` ViewModifier private struct cu @ViewBuilder if/else
58

            
59
2. **MeterSettingsView.swift**
60
   - Adaugat `@Environment(\.dismiss)` si `isMacIPadApp` static const
61
   - Body rewritten cu VStack + conditional `macSettingsHeader`
62
   - Adaugat `IOSOnlySettingsNavBar` ViewModifier private struct
63

            
64
3. **Model layer** (executat anterior, nu direct pentru fix), dar relevant:
65
   - BluetoothSerial.swift: RSSI averaging
66
   - Meter.swift: @Published dedup
67
   - Measurements.swift: objectWillChange fix
68

            
69
## Test status
70
- ✅ Compileaza fara erori
71
- ✅ "Designed for iPad" mode pe macOS: MeterView si MeterSettingsView deschid fara blocaj
72
- ⚠️ Real iPad: NU am iPad disponibil sa verific ca problema nu apare (nu am device pentru test)
73
  - UIKit nav bar modifiers se aplica normal pe real iPad (path aplicarii e diferit din UIKit sandbox)
74
  - Expect sa fie OK dar nu am certitudine
75

            
76
## Commit-uri
77
- [recent] - UIKit NavBar bypass pe "Designed for iPad", MeterView + MeterSettingsView custom headers
78

            
79
## Verificare
80
- pe macOS Apple Silicon + "Designed for iPad": deschide app, selecteaza meter, apasa sa o deschizi
81
  - MeterView trebuie sa deschida fara freeze
82
  - MeterSettingsView (pe gear icon) trebuie sa deschida fara freeze
83
  - Back button sa revina la lista
84

            
85
## Lessons learned
86
- Nu toate feedback loop-urile ObservableObject sunt cauzate de frecventa datelor
87
- UIKit pe macOS "Designed for iPad" are bugs nedocumentate in observation tracking cand SwiftUI configureaza nav bar
88
- Catalyst (AppKit) nu are problema (toolbar separat)
89
- Workaround: bypass-ul ca sa nu setam nav bar modifiers pe Mac
90

            
91
## Notes pentru viitor
92
- Aceasta solutie functioneaza pe "Designed for iPad" + Catalyst
93
- Nu am testat pe real iPad (nu am device); expect sa fie OK din arhitectura (modifiers se aplica)
94
- Daca problema apare pe real iPad in viitor: mai multi custom headers nu scala; s-ar putea reveni la NavigationView + sheet-based nav (cum am facut pe Catalyst in issue #001)