Conflict-free Xcode Project Management
Tags: swift, xcode, ios
Published: 2021-10-05 10:31:19


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

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

## 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8rhh18je76s69swnedvw.png)

### Create a new Swift Package.
1. From your Xcode, go to File -> New -> Swift Package.
  ![Swift Package creation in menu bar](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qf9mbdg8ybxu61c4288m.png)
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l3br6wzsowangan1vmjo.png)
3. If you did it right, the Swift Package will be shown in the file explorer. 
  ![Swift Package is added to the Xcode project](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pwb01ggaetqwdsz6p1fz.png)

### 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jqj3jpfyce8a7vwe0s69.png)

### 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.
```swift
// 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
}
```

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.
```swift
class MyViewController: UIViewController {
  init() {
    super.init(nibName: nil, bundle: nil)
  }
}
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1tsunf8ln441glwu61j4.png)

you need to change the bundle argument.
```swift
super.init(nibName: nil, bundle: Bundle.module)
```
## 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.