如何阻止UIViewController旋转时调整大小?仿地图App底部菜单适配方案
Great question! I’ve dealt with this exact scenario when building a map-centric app, and the cleanest, most maintainable solution (no manual frame adjustments every rotation) is to use the container view controller pattern—this is exactly what Apple does for their Maps app’s persistent bottom menu.
The core idea is to split your UI into two separate view controllers, each handling their own rotation behavior:
- A "fixed container" VC that holds your non-resizing elements (like the bottom menu)
- A "rotatable content" VC that handles your adaptive content (like the map itself)
Here’s how to implement it step-by-step:
1. Build the Fixed Container VC
This VC will host your bottom menu and prevent itself from auto-rotating, keeping the menu’s size locked in place.
class FixedContainerVC: UIViewController { private let bottomMenu = UIView() override func viewDidLoad() { super.viewDidLoad() setupBottomMenu() addContentVC() } private func setupBottomMenu() { bottomMenu.backgroundColor = .systemGray6 bottomMenu.translatesAutoresizingMaskIntoConstraints = false view.addSubview(bottomMenu) // Lock the menu's position and size with Auto Layout NSLayoutConstraint.activate([ bottomMenu.leadingAnchor.constraint(equalTo: view.leadingAnchor), bottomMenu.trailingAnchor.constraint(equalTo: view.trailingAnchor), bottomMenu.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), bottomMenu.heightAnchor.constraint(equalToConstant: 88) // Fixed height, never changes ]) } // Disable auto-rotation for this VC so its view doesn't resize override var shouldAutorotate: Bool { return false } override var supportedInterfaceOrientations: UIInterfaceOrientationMask { return .portrait // Keep the container in portrait layout } }
2. Build the Rotatable Content VC
This VC will hold your map (or other adaptive content) and handle rotation normally.
class RotatableContentVC: UIViewController { override var shouldAutorotate: Bool { return true } override var supportedInterfaceOrientations: UIInterfaceOrientationMask { return .allButUpsideDown // Allow all common orientations } // Add your map or content views here, with constraints that adapt to rotation override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .systemBlue // Example: Add a map view pinned to all edges let mapView = MKMapView() mapView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(mapView) NSLayoutConstraint.activate([ mapView.leadingAnchor.constraint(equalTo: view.leadingAnchor), mapView.trailingAnchor.constraint(equalTo: view.trailingAnchor), mapView.topAnchor.constraint(equalTo: view.topAnchor), mapView.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) } }
3. Combine the Two VCs
Add the rotatable content VC as a child of the fixed container VC, and pin its view to fill the space above the bottom menu.
// Inside FixedContainerVC private func addContentVC() { let contentVC = RotatableContentVC() addChild(contentVC) contentVC.view.translatesAutoresizingMaskIntoConstraints = false view.addSubview(contentVC.view) contentVC.didMove(toParent: self) NSLayoutConstraint.activate([ contentVC.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), contentVC.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), contentVC.view.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), contentVC.view.bottomAnchor.constraint(equalTo: bottomMenu.topAnchor) ]) }
Why this beats manual rotation adjustments:
- Apple-approved pattern: Container VCs are the official way to split complex UI logic, making your code easier to maintain and extend.
- No hacky frame tweaks: Everything relies on Auto Layout and the view controller lifecycle—no need to hardcode frame changes in
viewWillTransition(to:with:). - Clean separation: Your fixed UI and adaptive content are completely decoupled, so changes to one won’t break the other.
Bonus: iOS 16+ Global Size Lock (for full-screen fixed size)
If you need your entire view controller to stay the same size regardless of rotation (not just a single menu), iOS 16 introduced window scene size restrictions. This locks the entire window to a fixed size, leaving black bars around it when rotated:
// Add this in your app delegate or scene delegate if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene { scene.sizeRestrictions?.minimumSize = CGSize(width: 375, height: 667) scene.sizeRestrictions?.maximumSize = CGSize(width: 375, height: 667) }
This is less flexible than the container pattern, but perfect for apps that need a fixed-size UI no matter the device orientation.
内容的提问来源于stack exchange,提问作者tagyro

