After a long while, I have finally adopted XcodeGen across my projects.

I have been meaning to do this for a long time, having used RubyMotion and loved how it manages macOS/iOS projects with a Rakefile. e.g.:

Motion::Project::App.setup do |app|
  app.name = 'My app name'
  app.identifier = 'my.bundle.id'
  app.short_version = '1.19.0'
  app.version = '130'
  app.deployment_target = '8.0'
  app.archs['iPhoneOS'] << 'arm64'
  app.archs['iPhoneSimulator'] << 'x86_64'
  app.interface_orientations = [:portrait]
  app.info_plist['UIRequiresPersistentWiFi'] = true
  app.info_plist['UILaunchStoryboardName'] = 'Launch Screen'
  app.info_plist['CFBundleIcons'] = { 'CFBundlePrimaryIcon' => { 'UIPrerenderedIcon' => true }}
  app.info_plist['CFBundleIcons'] = {
    'CFBundlePrimaryIcon' => {
      'CFBundleIconName' => 'AppIcon',
      'CFBundleIconFiles' => ['AppIcon20x20', 'AppIcon29x29', 'AppIcon40x40', 'AppIcon60x60']
    }
  }

…and to a certain extent, the usage of package.json in the JavaScript world.

XcodeGen is a tool that lets you define your project in a project.yml file and then generate the Xcode project for you. So you would check in project.yml and not your .xcodeproj and .xcworkspace files (directories). You run xcodegen, and it re-generates an update-to-date .xcodeproj. If you use CocoaPods, run pod install again each time you run xcodegen.

The benefits of using XcodeGen’s project.yml over manually editing Xcode project and workspace files are:

  • No more pesky merge conflicts in your project files
  • Much easier to copy and paste and edit an .yml file
  • don’t need to worry about moving and organising your source files within Xcode. Just create/move/rename files and re-run xcodegen

.gitignore dance for Package.resolved

The docs are pretty good. There’s one thing to be careful of if you use SwiftPM because you’d want to check in Package.resolved, but it’s in the project/workspace directories. So in git, you’d have to do this dance in .gitignore if you use a project:

# xcodegen. Can't do only `*.xcodeproj` otherwise `Package.resolved` can't be made an exception as the directory isn't scanned
*.xcodeproj
*.xcworkspace/*
## We want the SwiftPM lock file
*.xcworkspace/xcshareddata/*
!*.xcworkspace/xcshareddata/
!*.xcworkspace/xcshareddata/swiftpm
!*.xcworkspace/xcshareddata/swiftpm/Package.resolved

and if you have a workspace (generally because you use CocoaPods; yes, you can mix SPM and CocoaPods):

# xcodegen. Can't do only `*.xcworkspace` otherwise `Package.resolved` can't be made an exception as the directory isn't scanned
*.xcodeproj
*.xcworkspace/*
## We want the SwiftPM lock file
*.xcworkspace/xcshareddata/*
!*.xcworkspace/xcshareddata/
!*.xcworkspace/xcshareddata/swiftpm
!*.xcworkspace/xcshareddata/swiftpm/Package.resolved