DEV Community

Cover image for Conflict-free Xcode Project Management
Samuel Edwin
Samuel Edwin

Posted on

Conflict-free Xcode Project Management

There is one pain that every iOS developers face, especially when they work in a team.

Xcode merge conflicts.

It is one of the most dreadful and time wasting activity that nobody likes to do. This pain only stings more the bigger your team and codebase gets.

Why does it keep happening?

Xcode keeps track of all source files that needs to be compiled within this project.pbxproj. It is a plain text file which has the content like below.

// !$*UTF8*$!
{
    archiveVersion = 1;
    classes = {
    };
    objectVersion = 50;
    objects = {

/* Begin PBXBuildFile section */
        923EF325270C588D0058CE0F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 923EF324270C588D0058CE0F /* AppDelegate.swift */; };
        923EF327270C588D0058CE0F /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 923EF326270C588D0058CE0F /* SceneDelegate.swift */; };
        923EF329270C588D0058CE0F /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 923EF328270C588D0058CE0F /* ViewController.swift */; };
        923EF32C270C588D0058CE0F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 923EF32A270C588D0058CE0F /* Main.storyboard */; };
        923EF32E270C588F0058CE0F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 923EF32D270C588F0058CE0F /* Assets.xcassets */; };
        923EF331270C588F0058CE0F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 923EF32F270C588F0058CE0F /* LaunchScreen.storyboard */; };
        923EF33A270C58E00058CE0F /* ShopViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 923EF338270C58E00058CE0F /* ShopViewController.swift */; };
        923EF33B270C58E00058CE0F /* ShopViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 923EF339270C58E00058CE0F /* ShopViewController.xib */; };
/* End PBXBuildFile section */
Enter fullscreen mode Exit fullscreen mode

You don't even need to be in a team to experience this. If you work on multiple branches, the conflict often shows up whenever you merge them.

CONFLICT (content): Merge conflict in SadApp.xcodeproj/project.pbxproj
Automatic merge failed; fix conflicts and then commit the result.
Enter fullscreen mode Exit fullscreen mode

Have you ever wished for the merge conflicts to go away?

I do, all the time. I have worked on Android and web projects in the past. The conflicts happen much less frequently, if they happen at all.

If you have been looking for a solution, you are lucky my friend. There is a hope to end this madness.

Swift Package Manager

Swift Package Manager(SPM) has existed for years since Swift 3.0.

We seldom see its usage outside of server side and command line Swift apps. The reason for this is because it used to have bad support for iOS development.

Things have changed though since the release of Xcode 12.

Xcode 12 adds a first class support for using Swift Package Manager in iOS app development.
All the pains I mentioned before instantly went away after I used it.

This is a powerful technique yet still unknown to many people, given how new it is.

Now I want to show you how to use it.

Let's give it a try

Create a new iOS project, as usual

You know the drill:

  1. Open Xcode
  2. Create a new iOS app. I'll just name it MyApp in this demo. Yes I have a wonderful naming sense. Image showing an Xcode project creation

Create a new Swift Package.

  1. From your Xcode, go to File -> New -> Swift Package. Swift Package creation in menu bar
  2. Give a name for your package. Some important things to notice:
    • Make sure that the package will be stored inside your project folder as shown in the highlighted green box below.
    • Select your previously created project in the Add to section as shown in the yellow box below. Swift Package creation dialogue
  3. If you did it right, the Swift Package will be shown in the file explorer. Swift Package is added to the Xcode project

Import the package from the app target.

In order to import your Swift package from your app, go to your app's Build Phases section, and add your package target in the Link Binary With Libraries subsection.

Add the image through the Build Phase setting in Xcode

Build the project

Let's see if things work as they should:

  1. Add new code inside Sources/MyAppPackage.
  2. Import the code from your app target.
// MyAppPackage/Sources/MyAppPackage/MyAppPackage.swift
public func hello() {
  print("Hello from Swift Package")    
}

// MyApp/AppDelegate.swift
import MyAppPackage

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
  hello()
  return true
}
Enter fullscreen mode Exit fullscreen mode

If you see the Hello from Swift Package message from your console, then that means you have used a Swift Package code from inside your app. Congratulations 🤝.

One important thing here project.pbxproj does not change no matter how many files you put in your package. That's why this technique is miles better compared to the old one.

What should I put in the Swift Package?

I'd put pretty much everything there except for Info.plist, Main and LaunchScreen storyboards.

For everything else I'll put them into the Swift Package: Swift files, Xibs, Storyboards, image assets.

Don't lose your assets

If you use Xibs, Storyboards, or image assets, you need to do a small tweak in your code. Pretty much things that related to bundles.

When you create a view controller, this is the initializer that a lot people use.

class MyViewController: UIViewController {
  init() {
    super.init(nibName: nil, bundle: nil)
  }
}
Enter fullscreen mode Exit fullscreen mode

Notice that I put nil in the bundle argument. This means that the main Bundle will be used to locate the Nib resource.

If your Nib is located in a Swift Package like below,
The Xib is located in a Swift Package

you need to change the bundle argument.

super.init(nibName: nil, bundle: Bundle.module)
Enter fullscreen mode Exit fullscreen mode

Conclusion

Swift Package Manager is a superior way to manage your Xcode projects because you don't have to deal with merge conflicts that often anymore. Things just work.

If you are starting a new project, I'd recommend you to use it right away.

If you are working in an existing project, better migrate them to Swift Package Manager as soon as you can.

If you have questions, let me know in the comments section. I'd love to answer your confusions.

Oldest comments (0)