diff --git a/pcsx2/vtlb.cpp b/pcsx2/vtlb.cpp index 8f8104c409..02f9eaf46c 100644 --- a/pcsx2/vtlb.cpp +++ b/pcsx2/vtlb.cpp @@ -1006,6 +1006,13 @@ static void vtlb_RemoveFastmemMapping(u32 vaddr) static void vtlb_RemoveFastmemMappings(u32 vaddr, u32 size) { + // When the 4 GB fastmem area reservation fails (e.g. on low-memory iOS + // devices like the iPhone SE 2), s_fastmem_virtual_mapping is never + // resized and remains empty. Indexing it would dereference NULL, so bail + // out early — there are no mappings to remove. + if (s_fastmem_virtual_mapping.empty()) + return; + pxAssert((vaddr & VTLB_PAGE_MASK) == 0); pxAssert(size > 0 && (size & VTLB_PAGE_MASK) == 0); diff --git a/platforms/ios/app/src/main/swift/Models/SwiftUIHost.swift b/platforms/ios/app/src/main/swift/Models/SwiftUIHost.swift index 86df2e97a1..0458b8cfd0 100644 --- a/platforms/ios/app/src/main/swift/Models/SwiftUIHost.swift +++ b/platforms/ios/app/src/main/swift/Models/SwiftUIHost.swift @@ -55,6 +55,30 @@ class ARMSX2HostingController: UIHostingController { applyNativeContentScale(to: view) } + /// Force SwiftUI to re-evaluate its layout when the device rotates. + /// + /// This hosting controller is a child of SDL's root view controller. While + /// UIKit does forward `viewWillTransition` to child controllers, + /// `UIHostingController`'s internal layout engine sometimes fails to + /// invalidate its `GeometryReader` contents promptly — especially when the + /// view is pinned via Auto Layout constraints rather than living directly + /// under the window. Without this nudge, SwiftUI keeps stale geometry after + /// rotation, producing broken layouts (black bars, cropped viewports, + /// misplaced touch controls). We force a re-evaluation by toggling the + /// `rootView` on the animation coordinator so the re-layout rides the + /// standard rotation animation block. + override func viewWillTransition(to size: CGSize, with coordinator: any UIViewControllerTransitionCoordinator) { + super.viewWillTransition(to: size, with: coordinator) + // Re-assigning rootView forces UIHostingController to invalidate its + // internal sizing cache and re-measure for the new container size. + let current = rootView + coordinator.animate(alongsideTransition: { _ in + self.rootView = current + self.view.setNeedsLayout() + self.view.layoutIfNeeded() + }) + } + @objc private func systemChromeNeedsUpdate() { setNeedsStatusBarAppearanceUpdate() setNeedsUpdateOfHomeIndicatorAutoHidden() diff --git a/platforms/ios/app/src/main/swift/Views/QuickMenuView.swift b/platforms/ios/app/src/main/swift/Views/QuickMenuView.swift index 8a638617cc..7f90a677e4 100644 --- a/platforms/ios/app/src/main/swift/Views/QuickMenuView.swift +++ b/platforms/ios/app/src/main/swift/Views/QuickMenuView.swift @@ -97,12 +97,13 @@ struct QuickMenuView: View { /// Two columns only when the card is wide enough to keep both columns comfortable. /// iPad portrait and short/small phone-landscape devices fall back to one column. - /// iPhone portrait uses two columns when wide enough (e.g. plus/max in portrait) - /// so the pause menu content is compact and readable without scrolling. + /// iPhone portrait always uses a single-column scroll layout — the screen is + /// too narrow for two comfortable columns, even on Plus/Max devices. Landscape + /// and iPad still get two columns when wide enough. private func supportsTwoColumns(width: CGFloat, height: CGFloat) -> Bool { switch variant { case .phonePortrait: - return width >= 390 + return false case .ipadTwoColumn: return width >= 500 && height >= 320 case .phoneLandscape: