Build a minimal iOS project structure from scratch, supporting SwiftUI, UIKit (Swift/OC), pure code without Storyboard.When creating an empty project, a .xcodeproj must be generated directly so that the project can be opened directly with Xcode, without relying on the user having XcodeGen installed locally.
Agent Execution Requirements (Required Reading)
When creating an iOS empty projectmustcomplete the following at the same time:
-
Generate source code and resources: under
{ProjectName}/{ProjectName}/create the source files corresponding to the template (main/AppDelegate/SceneDelegate/ViewController, Info.plist, etc.).UIKit projects (Swift/OC) must include LaunchScreen.storyboard, otherwise non-fullscreen (black bars) will appear on notch-screen devices. -
Generate .xcodeproj directly: in the project root directory (at the same level as the inner
{ProjectName}/) create{ProjectName}.xcodeproj/project.pbxproj, with content in valid Xcode project format, including:- PBXBuildFile: one entry for each .m or .swift that needs to participate in compilation;UIKit projects must include the Resources BuildFile for LaunchScreen.storyboard。
- PBXFileReference: one entry for each file (including .h, .m/.swift, Info.plist, .app);UIKit projects include LaunchScreen.storyboard; the product's explicitFileType is
wrapper.application, path is{ProjectName}.app。 - PBXGroup: root Group, Products, and the source directory Group (path =
{ProjectName}), with child Groups as needed (such as Models, Views, etc.);the source Group must include LaunchScreen.storyboard。 - PBXSourcesBuildPhase: list the BuildFile for all .m or .swift.
- PBXFrameworksBuildPhase: files may be empty.
- PBXResourcesBuildPhase:UIKit projects must include the BuildFile for LaunchScreen.storyboard; if Assets.xcassets is included, add the corresponding BuildFile as well.
- PBXNativeTarget: buildPhases includes Sources, Frameworks, Resources; productReference points to the .app's FileReference; productName = ProjectName.
- PBXProject: rootObject; mainGroup; targets includes the aforementioned NativeTarget.
- XCBuildConfiguration(Project and Target each Debug/Release):
IPHONEOS_DEPLOYMENT_TARGET = 15.0;INFOPLIST_FILE = {ProjectName}/Info.plist;PRODUCT_BUNDLE_IDENTIFIER = com.example.{ProjectName};TARGETED_DEVICE_FAMILY = "1,2";GENERATE_INFOPLIST_FILE = NO(when using a custom Info.plist).
For Swift addSWIFT_VERSION = "5.0". Do not reference a nonexistent Asset Catalog (if there is no Assets, do not write ASSETCATALOG_COMPILER_*).
-
Directory convention: the project root directory structure is
{ProjectName}/{ProjectName}.xcodeproj/and{ProjectName}/{ProjectName}/(source directory), and the Group's path and relative paths in pbxproj are consistent with this.
After completion, the user can directly open {ProjectName}.xcodeproj compile and run, without needing to execute xcodegen generate。
(If the user explicitly requests using XcodeGen, project.yml may additionally be provided along with a note that xcodegen generateneeds to be executed locally; otherwise, directly generating .xcodeproj takes precedence.)
Optional: Use XcodeGen to generate the project
If the user already has XcodeGen installed or explicitly requests maintaining the project with YAML, project.ymlmay be provided, and the user executes xcodegen generate in the project root directory to generate .xcodeproj.
- Install XcodeGen:
brew install xcodegen(or Mint:mint install yonaskolb/XcodeGen) - Create the project directory: such as
MyApp/, and under it create the subdirectoryMyApp/as the target source directory - In the project root directory(at the same level as
MyApp/)Createproject.yml, choose one according to the template (see below) - Place the source files of the corresponding templateinto
MyApp/MyApp/(and Assets.xcassets) - Run: in the directory containing
project.ymlrunxcodegen generate, generateMyApp.xcodeproj - Open:
open MyApp.xcodeproj
project.yml example (SwiftUI)
Place project.ymlin the project root directory, the target source directory is MyApp:
name: MyApp
options:
bundleIdPrefix: com.example
deploymentTarget:
iOS: "15.0"
targets:
MyApp:
type: application
platform: iOS
sources: [MyApp]
settings:
base:
SWIFT_VERSION: "5.0"
TARGETED_DEVICE_FAMILY: "1,2"
project.yml example (UIKit Swift, code only)
name: MyApp
options:
bundleIdPrefix: com.example
deploymentTarget:
iOS: "15.0"
targets:
MyApp:
type: application
platform: iOS
sources: [MyApp]
info:
path: MyApp/Info.plist
properties:
CFBundleDisplayName: 我的应用
UILaunchStoryboardName: LaunchScreen
UIApplicationSceneManifest:
UIApplicationSupportsMultipleScenes: false
UISceneConfigurations:
UIWindowSceneSessionRoleApplication:
- UISceneConfigurationName: Default Configuration
UISceneDelegateClassName: "$(PRODUCT_MODULE_NAME).SceneDelegate"
settings:
base:
SWIFT_VERSION: "5.0"
TARGETED_DEVICE_FAMILY: "1,2"
When it is necessary to complete in MyApp/Info.plist or keep only Scene-related keys, you can also point info.path to that plist, and XcodeGen will automatically set INFOPLIST_FILE。UIKit projects still need to create MyApp/MyApp/ under LaunchScreen.storyboard and include it in sources/resources, otherwise it will not be full screen on notched devices.
project.yml example (UIKit Objective-C, code only)
name: MyApp
options:
bundleIdPrefix: com.example
deploymentTarget:
iOS: "15.0"
targets:
MyApp:
type: application
platform: iOS
sources: [MyApp]
info:
path: MyApp/Info.plist
properties:
CFBundleDisplayName: 我的应用
UILaunchStoryboardName: LaunchScreen
UISceneDelegateClassName: SceneDelegate
UIApplicationSceneManifest:
UIApplicationSupportsMultipleScenes: false
UISceneConfigurations:
UIWindowSceneSessionRoleApplication:
- UISceneConfigurationName: Default Configuration
UISceneDelegateClassName: SceneDelegate
settings:
base:
TARGETED_DEVICE_FAMILY: "1,2"
For OC projects, set UISceneDelegateClassName to SceneDelegate(do not use $(PRODUCT_MODULE_NAME).SceneDelegate), otherwise it may report an error at runtime.Likewise, LaunchScreen.storyboard。
Select template
Before creating, have the developer choose: Swift or Objective-C?
| Language | Type | Applicable scenario |
|---|---|---|
| Swift | SwiftUI | Preferred for new projects, declarative UI, less code |
| Swift | UIKit | Code only, no Storyboard |
| Objective-C | UIKit | Maintaining old projects, integrating OC third-party libraries, team prefers OC |
SwiftUI empty project (Swift)
The following source code and resources are used together with project.yml(SwiftUI) above: place them into MyApp/MyApp/ and MyApp/MyApp/Assets.xcassets/, then run xcodegen generate. It can also be created manually with Xcode only.
Directory structure
MyApp/
├── MyApp/
│ ├── MyAppApp.swift # @main 入口
│ ├── ContentView.swift # 根视图
│ └── Assets.xcassets/
└── MyApp.xcodeproj/
1. App entry MyAppApp.swift
import SwiftUI
@main
struct MyAppApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
2. Root view ContentView.swift
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Hello, World!")
}
}
3. Assets.xcassets
Create the Assets.xcassets directory, containing Contents.json:
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
UIKit empty project (Swift, code only)
The following source code is used together with project.yml(UIKit Swift) : place the source code into MyApp/MyApp/, and Info.plist can be generated by XcodeGen's info or created manually.
Directory structure
MyApp/
├── MyApp/
│ ├── AppDelegate.swift
│ ├── SceneDelegate.swift
│ ├── ViewController.swift
│ ├── LaunchScreen.storyboard # 必须,否则刘海屏上非全屏(黑边)
│ └── Assets.xcassets/
└── MyApp.xcodeproj/
1. AppDelegate.swift
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
return true
}
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
}
2. SceneDelegate.swift
import UIKit
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
window = UIWindow(windowScene: windowScene)
window?.rootViewController = ViewController()
window?.makeKeyAndVisible()
}
}
3. ViewController.swift
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
}
}
4. LaunchScreen.storyboard (required)
UIKit projectsmustinclude a launch screen, otherwise on iPhone X and later models it will display in compatibility mode, with black bars at the top and bottom or on the left and right (not full screen). Under MyApp/MyApp/ , create LaunchScreen.storyboard, the content can be:
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21701" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21678"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" customClass="UIViewController" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
</document>
And in Info.plist , add:
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
In pbxproj, you need: PBXFileReference (LaunchScreen.storyboard), add the file to PBXGroup, add it to PBXBuildFile in Resources, and add that BuildFile to the files of PBXResourcesBuildPhase.
5. Info.plist (Scene configuration)
In Info.plist , it needs to include (UILaunchStoryboardName see the previous section).When using a custom Info.plist and GENERATE_INFOPLIST_FILE = NO, CFBundleExecutable must be included, otherwise installing on a real device will report "missing or invalid CFBundleExecutable":
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleDisplayName</key>
<string>我的应用</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
</dict>
</array>
</dict>
</dict>
If Baidu Maps is integrated and location is used, add NSLocationWhenInUseUsageDescription。
UIKit empty project (Objective-C, code only)
The following source code is used together with project.yml(UIKit OC) : place the source code into MyApp/MyApp/, and Info.plist can be generated by XcodeGen's info or created manually.
Directory structure
MyApp/
├── MyApp/
│ ├── main.m
│ ├── AppDelegate.h
│ ├── AppDelegate.m
│ ├── SceneDelegate.h
│ ├── SceneDelegate.m
│ ├── ViewController.h
│ ├── ViewController.m
│ ├── LaunchScreen.storyboard # 必须,否则刘海屏上非全屏(黑边)
│ └── Assets.xcassets/
└── MyApp.xcodeproj/
1. main.m
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
2. AppDelegate.h / AppDelegate.m
// AppDelegate.h
#import <UIKit/UIKit.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@end
// AppDelegate.m
#import "AppDelegate.h"
#import "SceneDelegate.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 若集成百度地图:[BMKMapManager setAgreePrivacy:YES]; // 类方法,勿用 sharedInstance
// [[BMKMapManager sharedInstance] start:@"YOUR_AK" generalDelegate:nil];
return YES;
}
- (UISceneConfiguration *)application:(UIApplication *)application configurationForConnectingSceneSession:(UISceneSession *)connectingSceneSession options:(UISceneConnectionOptions *)options {
return [[UISceneConfiguration alloc] initWithName:@"Default Configuration" sessionRole:connectingSceneSession.role];
}
@end
3. SceneDelegate.h / SceneDelegate.m
// SceneDelegate.h
#import <UIKit/UIKit.h>
@interface SceneDelegate : UIResponder <UIWindowSceneDelegate>
@property (strong, nonatomic) UIWindow * window;
@end
// SceneDelegate.m
#import "SceneDelegate.h"
#import "ViewController.h"
@implementation SceneDelegate
- (void)scene:(UIScene *)scene willConnectToSession:(UISceneSession *)session options:(UISceneConnectionOptions *)connectionOptions {
UIWindowScene *windowScene = (UIWindowScene *)scene;
self.window = [[UIWindow alloc] initWithWindowScene:windowScene];
self.window.rootViewController = [[ViewController alloc] init];
[self.window makeKeyAndVisible];
}
@end
4. ViewController.h / ViewController.m
// ViewController.h
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@end
// ViewController.m
#import "ViewController.h"
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor systemBackgroundColor];
}
@end
5. LaunchScreen.storyboard (required)
Same as the "UIKit Empty Project (Swift)" section:Mustunder MyApp/MyApp/ create LaunchScreen.storyboard(for content, see the same section in the Swift template), and in Info.plist add:
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
In pbxproj, LaunchScreen.storyboard must be added to PBXFileReference, PBXGroup, PBXBuildFile (Resources), and PBXResourcesBuildPhase.
6. Info.plist (Scene configuration)
Must include UIApplicationSceneManifest、UISceneDelegateClassName as well as UILaunchStoryboardName(see the previous section).When using a custom Info.plist, CFBundleExecutable must be included, otherwise installation on a real device will report "missing or invalid CFBundleExecutable".Objective-C projectmust use SceneDelegate(not $(PRODUCT_MODULE_NAME).SceneDelegate), otherwise runtime error "could not load class with name \"xxx.SceneDelegate\"" will occur.
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>UISceneDelegateClassName</key>
<string>SceneDelegate</string>
If Baidu Map is integrated later: must configure CFBundleDisplayName, otherwise the SDK fails to start. When using location, NSLocationWhenInUseUsageDescriptionis required. See the baidu-map-ios-sdk skill for details.
<key>CFBundleDisplayName</key>
<string>我的应用</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>需要获取您的位置以展示地图</string>
Baidu Map integration connection
If creating an empty project for integrating the Baidu Map SDK:
| Select | Recommended |
|---|---|
| Language | Objective-C(the SDK is OC, CocoaPods integration is smoother) |
| Info.plist | Configure in advance CFBundleDisplayName、NSLocationWhenInUseUsageDescription |
| Subsequent steps | Use the baidu-map-ios-sdk skill: Podfile,pod install, AppDelegate initializes BMKMapManager |
Xcode project file key points
- Default requirement: when creating an empty project, must be generated directly
{ProjectName}.xcodeproj/project.pbxproj(see "Agent execution requirements" above), so that the project can be opened directly in Xcode without the user installing or running XcodeGen. - pbxproj key points: correctly configure PBXNativeTarget, PBXBuildFile, PBXFileReference, PBXGroup, PBXSourcesBuildPhase,PBXResourcesBuildPhase (UIKit projects must include LaunchScreen.storyboard), XCBuildConfiguration; Build Settings must include INFOPLIST_FILE, PRODUCT_BUNDLE_IDENTIFIER, TARGETED_DEVICE_FAMILY, IPHONEOS_DEPLOYMENT_TARGET (15.0 recommended); Swift projects add SWIFT_VERSION. Do not reference nonexistent resources (e.g., if there is no Assets, do not write ASSETCATALOG_COMPILER_*).
- Optional: if the user explicitly requests maintaining it with YAML, project.yml can additionally be provided, and state that it needs to be executed locally
xcodegen generate。 - Alternative: create a new App project in Xcode, then delete unnecessary files such as Storyboard.
Quick command (Swift Package style)
If only a Swift source code skeleton is needed, SPM can be used to initialize:
mkdir MyApp && cd MyApp
swift package init --type executable
Note: SPM generates a command-line executable, not an iOS App. An iOS App still requires an Xcode project.
More references
- For XcodeGen installation, common commands, Build Settings, etc., see reference.md
Notes
- Before creation: if the user has not specified a language, proactively ask whether to choose Swift or Objective-C
- Launch Screen (required): UIKit empty project (Swift/OC)mustinclude
LaunchScreen.storyboardand configure it in Info.plistUILaunchStoryboardName, otherwise a non-full-screen display (black bars) will occur on devices with a notch. For SwiftUI projects, this is handled by the system and does not need to be provided separately. - CFBundleExecutable (required when using a custom Info.plist): When using a custom Info.plist and
GENERATE_INFOPLIST_FILE = NO, you must configureCFBundleExecutablein Info.plist as$(EXECUTABLE_NAME), otherwise installing on a real device will report "missing or invalid CFBundleExecutable" and installation will fail (CoreDeviceError 3002). - SwiftUI projects do not require Scene configuration in Info.plist; Xcode handles it automatically
- Pure code UIKit projects need to remove
UIMainStoryboardFileand other Storyboard-related keys from Info.plist, butkeep UILaunchStoryboardName = LaunchScreen - The minimum supported version is recommended to be iOS 15+ to cover features such as Swift Concurrency