# IOS Empty Project

> Creates minimal iOS project structure from scratch. Must directly generate .xcodeproj (project.pbxproj) so the project can be opened in Xcode without XcodeGen. Supports Swift (SwiftUI/UIKit) and Objective-C (UIKit). Use when the user wants to create an iOS empty project, start a new iOS app, scaffold iOS app structure, or mentions iOS 空工程. Optimized for Baidu Map SDK integration (Info.plist CFBundleDisplayName, AppDelegate placeholder).

- Skill: `baidu-maps/ios-empty-project` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add baidu-maps/ios-empty-project`
- Raw SKILL.md: https://api.skillmd.com/api/skills/baidu-maps/ios-empty-project/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: baidu-maps (https://skillmd.com/u/baidu-maps)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/baidu-maps/ios-empty-project

---


# iOS 空工程

从零搭建最小化 iOS 项目结构，支持 SwiftUI、UIKit（Swift/OC），纯代码无 Storyboard。**空工程创建时必须直接生成 .xcodeproj，使工程可直接用 Xcode 打开，不依赖用户本机安装 XcodeGen。**

## Agent 执行要求（必读）

创建 iOS 空工程时**必须**同时完成：

1. **生成源码与资源**：在 `{ProjectName}/{ProjectName}/` 下创建对应模板的源码文件（main/AppDelegate/SceneDelegate/ViewController、Info.plist 等）。**UIKit 工程（Swift/OC）必须包含 LaunchScreen.storyboard**，否则在刘海屏设备上会出现非全屏（黑边）。
2. **直接生成 .xcodeproj**：在工程根目录（与内层 `{ProjectName}/` 同级）创建 **`{ProjectName}.xcodeproj/project.pbxproj`**，内容为合法 Xcode 工程格式，包含：
   - **PBXBuildFile**：每个需参与编译的 .m 或 .swift 一条；**UIKit 工程必须包含 LaunchScreen.storyboard 的 Resources BuildFile**。
   - **PBXFileReference**：每个文件（含 .h、.m/.swift、Info.plist、.app）一条；**UIKit 工程含 LaunchScreen.storyboard**；product 的 explicitFileType 为 `wrapper.application`，path 为 `{ProjectName}.app`。
   - **PBXGroup**：根 Group、Products、以及源码目录 Group（path = `{ProjectName}`），子 Group 按需（如 Models、Views 等）；**源码 Group 中需包含 LaunchScreen.storyboard**。
   - **PBXSourcesBuildPhase**：列出所有 .m 或 .swift 的 BuildFile。
   - **PBXFrameworksBuildPhase**：可为空 files。
   - **PBXResourcesBuildPhase**：**UIKit 工程必须包含 LaunchScreen.storyboard 的 BuildFile**；若含 Assets.xcassets 则再加入对应 BuildFile。
   - **PBXNativeTarget**：buildPhases 含 Sources、Frameworks、Resources；productReference 指向 .app 的 FileReference；productName = ProjectName。
   - **PBXProject**：rootObject；mainGroup；targets 含上述 NativeTarget。
   - **XCBuildConfiguration**（Project 与 Target 各 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`（使用自定义 Info.plist 时）。  
     Swift 时加 `SWIFT_VERSION = "5.0"`。不要引用不存在的 Asset Catalog（如无 Assets 则不写 ASSETCATALOG_COMPILER_*）。

3. **目录约定**：工程根目录结构为 `{ProjectName}/{ProjectName}.xcodeproj/` 与 `{ProjectName}/{ProjectName}/`（源码目录），pbxproj 中 Group 的 path 与相对路径与此一致。

完成后用户可直接 **`open {ProjectName}.xcodeproj`** 编译运行，无需执行 `xcodegen generate`。

（若用户明确要求使用 XcodeGen，可额外提供 project.yml 并说明需本地执行 `xcodegen generate`；否则以直接生成 .xcodeproj 为准。）

---

## 可选：使用 XcodeGen 生成项目

若用户已安装 XcodeGen 或明确要求用 YAML 维护工程，可提供 `project.yml`，由用户在工程根目录执行 `xcodegen generate` 生成 .xcodeproj。

1. **安装 XcodeGen**：`brew install xcodegen`（或 [Mint](https://github.com/yonaskolb/XcodeGen)：`mint install yonaskolb/XcodeGen`）
2. **创建工程目录**：如 `MyApp/`，其下建子目录 `MyApp/` 作为 target 源码目录
3. **在工程根目录**（与 `MyApp/` 同级）**创建 `project.yml`**，按模板选择其一（见下）
4. **将对应模板的源码文件**放入 `MyApp/MyApp/`（及 Assets.xcassets）
5. **执行**：在含 `project.yml` 的目录运行 `xcodegen generate`，生成 `MyApp.xcodeproj`
6. **打开**：`open MyApp.xcodeproj`

### project.yml 示例（SwiftUI）

工程根目录放 `project.yml`，target 源码目录为 `MyApp`：

```yaml
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 示例（UIKit Swift，纯代码）

```yaml
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"
```

需在 `MyApp/Info.plist` 中补全或仅保留 Scene 相关键时，也可将 `info.path` 指向该 plist，由 XcodeGen 自动设置 `INFOPLIST_FILE`。**UIKit 工程仍需在 `MyApp/MyApp/` 下创建 `LaunchScreen.storyboard` 并纳入 sources/resources，否则刘海屏上非全屏。**

### project.yml 示例（UIKit Objective-C，纯代码）

```yaml
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"
```

OC 工程 **UISceneDelegateClassName** 填 `SceneDelegate`（不要用 `$(PRODUCT_MODULE_NAME).SceneDelegate`），否则运行可能报错。**同样需提供 `LaunchScreen.storyboard`。**

---

## 选择模板

**创建前请让开发者选择**：Swift 还是 Objective-C？

| 语言 | 类型 | 适用场景 |
|------|------|----------|
| **Swift** | SwiftUI | 新项目首选，声明式 UI，代码量少 |
| **Swift** | UIKit | 纯代码无 Storyboard |
| **Objective-C** | UIKit | 维护老项目、接入 OC 第三方库、团队偏好 OC |

---

## SwiftUI 空工程（Swift）

以下源码与资源与上文 **project.yml（SwiftUI）** 配合使用：放入 `MyApp/MyApp/` 及 `MyApp/MyApp/Assets.xcassets/`，再执行 `xcodegen generate`。也可仅用 Xcode 手动创建。

### 目录结构

```
MyApp/
├── MyApp/
│   ├── MyAppApp.swift      # @main 入口
│   ├── ContentView.swift   # 根视图
│   └── Assets.xcassets/
└── MyApp.xcodeproj/
```

### 1. App 入口 `MyAppApp.swift`

```swift
import SwiftUI

@main
struct MyAppApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
```

### 2. 根视图 `ContentView.swift`

```swift
import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("Hello, World!")
    }
}
```

### 3. Assets.xcassets

创建 `Assets.xcassets` 目录，内含 `Contents.json`：

```json
{
  "info" : {
    "author" : "xcode",
    "version" : 1
  }
}
```

---

## UIKit 空工程（Swift，纯代码）

以下源码与 **project.yml（UIKit Swift）** 配合使用：源码放入 `MyApp/MyApp/`，Info.plist 可由 XcodeGen 的 `info` 生成或自建。

### 目录结构

```
MyApp/
├── MyApp/
│   ├── AppDelegate.swift
│   ├── SceneDelegate.swift
│   ├── ViewController.swift
│   ├── LaunchScreen.storyboard   # 必须，否则刘海屏上非全屏（黑边）
│   └── Assets.xcassets/
└── MyApp.xcodeproj/
```

### 1. AppDelegate.swift

```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

```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

```swift
import UIKit

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
    }
}
```

### 4. LaunchScreen.storyboard（必须）

UIKit 工程**必须**包含启动图，否则在 iPhone X 及以后机型上会以兼容模式显示，出现上下/左右黑边（非全屏）。在 `MyApp/MyApp/` 下创建 `LaunchScreen.storyboard`，内容可为：

```xml
<?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>
```

并在 **Info.plist** 中增加：

```xml
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
```

pbxproj 中需：PBXFileReference（LaunchScreen.storyboard）、PBXGroup 中加入该文件、PBXBuildFile 加入 Resources、PBXResourcesBuildPhase 的 files 中加入该 BuildFile。

### 5. Info.plist（Scene 配置）

在 `Info.plist` 中需包含（**UILaunchStoryboardName** 见上一节）。**使用自定义 Info.plist 且 GENERATE_INFOPLIST_FILE = NO 时，必须包含 CFBundleExecutable**，否则真机安装会报「missing or invalid CFBundleExecutable」：

```xml
<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>
```

若集成百度地图且使用定位，需添加 `NSLocationWhenInUseUsageDescription`。

---

## UIKit 空工程（Objective-C，纯代码）

以下源码与 **project.yml（UIKit OC）** 配合使用：源码放入 `MyApp/MyApp/`，Info.plist 可由 XcodeGen 的 `info` 生成或自建。

### 目录结构

```
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

```objc
#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

```objc
// 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

```objc
// 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

```objc
// 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（必须）

与「UIKit 空工程（Swift）」一节相同：**必须**在 `MyApp/MyApp/` 下创建 `LaunchScreen.storyboard`（内容见 Swift 模板同节），并在 **Info.plist** 中增加：

```xml
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
```

pbxproj 中需把 LaunchScreen.storyboard 加入 PBXFileReference、PBXGroup、PBXBuildFile（Resources）、PBXResourcesBuildPhase。

### 6. Info.plist（Scene 配置）

需包含 `UIApplicationSceneManifest`、`UISceneDelegateClassName` 以及 **UILaunchStoryboardName**（见上一节）。**使用自定义 Info.plist 时必须包含 CFBundleExecutable**，否则真机安装会报「missing or invalid CFBundleExecutable」。**Objective-C 工程**须用 `SceneDelegate`（不用 `$(PRODUCT_MODULE_NAME).SceneDelegate`），否则运行时报错「could not load class with name "xxx.SceneDelegate"」。

```xml
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>UISceneDelegateClassName</key>
<string>SceneDelegate</string>
```

**若后续集成百度地图**：必须配置 `CFBundleDisplayName`，否则 SDK 启动报错。使用定位时需 `NSLocationWhenInUseUsageDescription`。详见 baidu-map-ios-sdk 技能。

```xml
<key>CFBundleDisplayName</key>
<string>我的应用</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>需要获取您的位置以展示地图</string>
```

---

## 百度地图集成衔接

若创建空工程用于集成百度地图 SDK：

| 选择 | 建议 |
|------|------|
| 语言 | **Objective-C**（SDK 为 OC，CocoaPods 集成更顺畅） |
| Info.plist | 提前配置 `CFBundleDisplayName`、`NSLocationWhenInUseUsageDescription` |
| 后续步骤 | 使用 baidu-map-ios-sdk 技能：Podfile、`pod install`、AppDelegate 初始化 BMKMapManager |

---

## Xcode 项目文件要点

- **默认要求**：创建空工程时 **必须直接生成** `{ProjectName}.xcodeproj/project.pbxproj`（见上文「Agent 执行要求」），使工程可直接用 Xcode 打开，无需用户安装或执行 XcodeGen。
- **pbxproj 要点**：正确配置 PBXNativeTarget、PBXBuildFile、PBXFileReference、PBXGroup、PBXSourcesBuildPhase、**PBXResourcesBuildPhase（UIKit 工程必须含 LaunchScreen.storyboard）**、XCBuildConfiguration；Build Settings 需含 INFOPLIST_FILE、PRODUCT_BUNDLE_IDENTIFIER、TARGETED_DEVICE_FAMILY、IPHONEOS_DEPLOYMENT_TARGET（建议 15.0）；Swift 工程加 SWIFT_VERSION。不引用不存在的资源（如无 Assets 则不写 ASSETCATALOG_COMPILER_*）。
- **可选**：若用户明确要求用 YAML 维护，可额外提供 project.yml，并说明需本地执行 `xcodegen generate`。
- 备选：用 Xcode 新建 App 工程，再删除 Storyboard 等不需要的文件。

---

## 快速命令（Swift Package 风格）

若仅需 Swift 源码骨架，可用 SPM 初始化：

```bash
mkdir MyApp && cd MyApp
swift package init --type executable
```

注意：SPM 生成的是命令行可执行文件，不是 iOS App。iOS App 仍需 Xcode 工程。

---

## 更多参考

- XcodeGen 安装、常用命令、Build Settings 等见 [reference.md](reference.md)

## 注意事项

- **创建前**：若用户未指定语言，主动询问选择 Swift 还是 Objective-C
- **Launch Screen（必须）**：UIKit 空工程（Swift/OC）**必须**包含 `LaunchScreen.storyboard` 并在 Info.plist 中配置 `UILaunchStoryboardName`，否则在刘海屏设备上会出现非全屏（黑边）。SwiftUI 工程由系统处理，可不单独提供。
- **CFBundleExecutable（自定义 Info.plist 时必须）**：使用自定义 Info.plist 且 `GENERATE_INFOPLIST_FILE = NO` 时，必须在 Info.plist 中配置 `CFBundleExecutable` 为 `$(EXECUTABLE_NAME)`，否则真机安装会报「missing or invalid CFBundleExecutable」、无法安装（CoreDeviceError 3002）。
- SwiftUI 工程无需 Info.plist 中的 Scene 配置，Xcode 会自动处理
- UIKit 纯代码工程需在 Info.plist 中移除 `UIMainStoryboardFile` 等 Storyboard 相关键，但**保留 UILaunchStoryboardName = LaunchScreen**
- 最低支持版本建议 iOS 15+，以覆盖 Swift Concurrency 等特性

