@@ -74,14 +74,52 @@ final class AppOrientationPolicy {
7474 }
7575 }
7676
77+ /// The mask the policy should be reporting, given first-run state and any
78+ /// orientation a QA screenshot run has pinned.
79+ ///
80+ /// A pin outranks the ordinary policy for the life of the process. Without
81+ /// that, the next `.active` transition would hand the policy back and turn
82+ /// the window away from the orientation the sweep asked for.
83+ static func resolvedMask(
84+ onboardingComplete: Bool ,
85+ qaPinned: UIInterfaceOrientationMask ?
86+ ) -> UIInterfaceOrientationMask {
87+ qaPinned ?? mask ( onboardingComplete: onboardingComplete)
88+ }
89+
7790 func update( onboardingComplete: Bool ) {
78- let requested = Self . mask ( onboardingComplete: onboardingComplete)
91+ let requested = Self . resolvedMask (
92+ onboardingComplete: onboardingComplete,
93+ qaPinned: qaPinnedOrientations
94+ )
7995 let changed = supportedOrientations != requested
8096 supportedOrientations = requested
8197 RootOrientationSwizzle . installOnKeyWindow ( )
8298 applyToWindows ( forceGeometryUpdate: changed)
8399 }
84100
101+ #if DEBUG
102+ private var qaPinnedOrientations : UIInterfaceOrientationMask ?
103+
104+ /// Turns the window for a QA screenshot run and keeps it turned.
105+ ///
106+ /// `requestGeometryUpdate` on its own does not hold. iOS re-reads the root
107+ /// controller's `supportedInterfaceOrientations` the moment the request
108+ /// lands, and while that still answers `.allButUpsideDown` the physically
109+ /// portrait simulator wins and the window turns straight back — which is
110+ /// why every landscape column of the sweep came back "still portrait".
111+ /// Narrowing the policy to the requested orientation first is what makes
112+ /// the turn stick.
113+ func pinOrientationsForQA( _ mask: UIInterfaceOrientationMask ) {
114+ qaPinnedOrientations = mask
115+ supportedOrientations = mask
116+ RootOrientationSwizzle . installOnKeyWindow ( )
117+ applyToWindows ( forceGeometryUpdate: true )
118+ }
119+ #else
120+ private var qaPinnedOrientations : UIInterfaceOrientationMask ? { nil }
121+ #endif
122+
85123 private func applyToWindows( forceGeometryUpdate: Bool ) {
86124 let mask = supportedOrientations
87125 for scene in UIApplication . shared. connectedScenes. compactMap ( { $0 as? UIWindowScene } ) {
@@ -98,6 +136,13 @@ final class AppOrientationPolicy {
98136 orientationLog. error (
99137 " Geometry update failed: \( error. localizedDescription, privacy: . public) "
100138 )
139+ #if DEBUG
140+ // The screenshot sweep reads this key to explain a miss.
141+ UserDefaults . standard. set (
142+ error. localizedDescription,
143+ forKey: " ios.native.qaOrientationError "
144+ )
145+ #endif
101146 }
102147 }
103148 }
0 commit comments