Goal
Make a UIKit project fully storyboard-free by creating the UIWindow and the interface entirely in code.
Storyboards are convenient for prototyping, but they age poorly: the XML is hard to review in Git, merges collide frequently, and reusing a scene usually means copying a file. Moving to a code-only setup removes that friction — the window is created explicitly, every view is visible in source, and nothing is hidden behind a .storyboard file.
The examples below assume an Xcode project named uikit-demos, created from the iOS App template with storyboards enabled. Wherever that name appears — in file paths, Info.plist keys, or build commands — substitute your own project's name.
The migration takes five steps, from deleting files to verifying the build:
- Delete the storyboard files
- Remove the storyboard keys from
Info.plist - Create the window in
SceneDelegate - Clean up the build settings in
project.pbxproj - Verify with a few commands
1. Delete the storyboard files
Remove both the main storyboard and the launch screen storyboard:
rm -f uikit-demos/Base.lproj/Main.storyboard \
uikit-demos/Base.lproj/LaunchScreen.storyboard
rmdir uikit-demos/Base.lproj # Removes the directory only if empty; safe to ignore otherwiseThe rmdir is a convenience: it clears away the now-empty Base.lproj directory, and silently does nothing if it still contains other files.
2. Edit Info.plist
In uikit-demos/Info.plist, remove UISceneStoryboardFile and its <string>Main</string>:
<key>UISceneStoryboardFile</key>
<string>Main</string>This key told the scene system which storyboard to load as the initial interface. With it gone, the system no longer tries to instantiate a storyboard at launch.
Also add an empty launch screen dictionary, otherwise the app falls back to the legacy layout sizing:
<key>UILaunchScreen</key>
<dict/>Place it inside the top-level <dict>, after UIApplicationSceneManifest.
3. Create the window in SceneDelegate
The default scene(_:willConnectTo:options:) in uikit-demos/SceneDelegate.swift is an empty shell. Replace it with code that creates the window, attaches a root view controller, and makes it visible:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
let window = UIWindow(windowScene: windowScene)
window.rootViewController = ViewController()
window.makeKeyAndVisible()
self.window = window
}The guard protects against scenes that aren't window scenes, so the method returns early instead of force-casting.
4. Clean up the build settings in pbxproj
In uikit-demos.xcodeproj/project.pbxproj, delete the following two lines (one set for Debug and one for Release, four lines in total):
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UIMainStoryboardFile = Main;
These keys also point at storyboards, so leaving them behind would bring the references back on the next build. If you have extra configurations, repeat the same cleanup for each set.
5. Verify
Run these checks to confirm the project no longer references storyboards anywhere and still builds:
find . -name "*.storyboard" # Expected: no output
grep -rn "Storyboard\|UIMainStoryboardFile\|UILaunchStoryboardName\|UISceneStoryboardFile" \
uikit-demos uikit-demos.xcodeproj/project.pbxproj # Expected: no output
xcodebuild -project uikit-demos.xcodeproj -scheme uikit-demos \
-sdk iphonesimulator -destination 'generic/platform=iOS Simulator' \
-configuration Debug build CODE_SIGNING_ALLOWED=NOThe first two commands should print nothing; the last one runs a simulator build with code signing disabled, which is enough to confirm the project compiles.
What to expect
ViewController.swift is still empty at this point. The app will launch to a blank screen, but it works: it starts, has a window, and the scene lifecycle is intact. To show content, add views in viewDidLoad using code and Auto Layout:
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
// Build your interface with code and Auto Layout here
}From here the project is fully storyboard-free, and every future change — adding a view controller, wiring a layout, or adjusting a constraint — happens in plain Swift source, reviewable in Git like any other change.