ivan-magda/swiftui-interface-orientation
**Lock screen orientation per-view in SwiftUI. No UIKit subclassing, no hacks.**
The Problem
SwiftUI has no native way to lock orientation for specific views. You either lock the entire app in Info.plist, or dive into UIKit lifecycle callbacks scattered across AppDelegate, SceneDelegate, and view controllers.
This package gives you a single view modifier that just works.
Installation
Swift Package Manager
dependencies: [
.package(url: "https://github.com/ivan-magda/swiftui-interface-orientation.git", from: "1.2.0")
]Or in Xcode: File → Add Packages → paste the URL above.
Quick Start
1. Configure the manager (once, at app launch)
import SwiftUIInterfaceOrientation
@main
struct MyApp: App {
init() {
InterfaceOrientationManager.configure()
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}2. Wire up iOS orientation callbacks
class OrientationDelegate: NSObject, UIApplicationDelegate {
func application(
_ application: UIApplication,
supportedInterfaceOrientationsFor window: UIWindow?
) -> UIInterfaceOrientationMask {
InterfaceOrientationManager.shared.supportedInterfaceOrientations
}
}
@main
struct MyApp: App {
@UIApplicationDelegateAdaptor(OrientationDelegate.self) var delegate
init() {
InterfaceOrientationManager.configure()
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}3. Lock any view to specific orientations
struct PortraitOnlyView: View {
var body: some View {
Text("Locked to portrait")
.supportedInterfaceOrientations(.portrait)
}
}
struct LandscapeOnlyView: View {
var body: some View {
VideoPlayer(player: player)
.supportedInterfaceOrientations([.landscapeLeft, .landscapeRight])
}
}How It Works
The package uses a centralized InterfaceOrientationManager that tracks orientation constraints from all active views:
- When a view with
.supportedInterfaceOrientations()appears, it registers its constraint - When the view disappears, the constraint is removed
- The manager computes the intersection of all active constraints
- If constraints conflict (intersection is empty), it falls back to your app's defaults from Info.plist
This means nested views with different constraints "just work"—the most restrictive common orientation wins.
API
View Modifier
func supportedInterfaceOrientations(_ orientations: UIInterfaceOrientationMask) -> some ViewConfiguration
// Use defaults from Info.plist
InterfaceOrientationManager.configure()
// Or specify custom defaults
InterfaceOrientationManager.configure(
configuration: .init(defaultOrientations: .portrait)
)Orientation Masks
.portrait // Portrait only
.landscapeLeft // Landscape, home button on right
.landscapeRight // Landscape, home button on left
.portraitUpsideDown // Upside down (iPad only effectively)
.landscape // Both landscape orientations
.all // All orientations
.allButUpsideDown // All except upside downRequirements
- iOS 14.0+
- Swift 6
- Xcode 16.0+
License
MIT License. See LICENSE for details.
Contributing
Issues and PRs welcome.
Package Metadata
Repository: ivan-magda/swiftui-interface-orientation
Default branch: main
README: README.md