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:
- Open Xcode
- Create a new iOS app. I'll just name it
MyApp
in this demo. Yes I have a wonderful naming sense.
Create a new Swift Package.
- From your Xcode, go to File -> New -> Swift Package.
- 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.
- If you did it right, the Swift Package will be shown in the file explorer.
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.
Build the project
Let's see if things work as they should:
- Add new code inside
Sources/MyAppPackage
. - 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
}
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)
}
}
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,
you need to change the bundle argument.
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.
Top comments (0)