Storyboards are a very useful tool for developing user interfaces in iOS, macOS, tvOS, and watchOS. In addition to providing a way to visually lay out an interface with Auto Layout, they provide a way to build and see navigation and relationships between view controllers. When used well they can greatly simplify and reduce the amount of code developers need to interact with in a project. So why don’t all developers use storyboards?
One of the most commonly mentioned reasons for not using storyboards is teams with more than one developer working on a storyboard at the same time. Merge conflicts are a common occurrence in that case, and can be enough to discourage teams from using storyboards. Since storyboards are machine-generated XML, merge conflicts can be difficult to diagnose and resolve. However, in many cases they don’t have to be.
There are five general situations where merge conflicts are often encountered in storyboards. This post will take a look at each of these in detail to describe how the merge conflicts happen, how to avoid them, and how to resolve them.
One of the simplest merge conflicts happens when a new version of Xcode comes out. The merge conflict will happen when one user edits and commits a storyboard in an older version of Interface Builder, and another user edits and commits the same storyboard in the newer version of Interface Builder. Note that neither user needs to actually make any substantive changes to the storyboard – just opening the storyboard will frequently be enough to make a change.
Why is there a merge conflict? In this case, Xcode has updated the “document” tag and the “plugIn” tag in the storyboard’s source, as shown in Figure 1:
This merge conflict is the simplest to solve: it only matters which version is used if the changes used a new feature only supported in the new version, so the best solution is just to select the newer version to resolve the conflict and merge. If no real changes have happened, developers should revert the changes to the storyboard and not include it in a commit.
One thing to watch out for: the initial view controller for the storyboard is specified in the “document” tag as well – be sure to check that it didn’t change before resolving the merge conflict.
To avoid this type of merge conflict, it’s advisable for teams to coordinate Xcode updates.
Interface Builder will automatically update a storyboard seemingly every time a developer opens it; frequently the updates are caused by opening a storyboard on a different machine with a different monitor size or type (retina versus non-retina, for example). This type of change, if committed, can cause merge conflicts (see Figure 2).
The merge conflicts are pretty easily identifiable as small changes in the frame or layout information for views throughout the document. To avoid this type of conflict, don’t commit automatic changes reflexively. Developers should evaluate if they actually made a change to a storyboard, and only include it in the commit if they did. If a merge conflict of this type is encountered, the simplest approach is to review the conflicts, and if they are minor layout adjustments, select the newest change to resolve the conflict. If the change also shows a change in constraints nearby (you can be certain that is the case by checking whether the “id” of the object with an updated rect is referenced in the updated constraint), it’s likely the the layout change was due to updated constraints and should be selected when resolving the merge conflict.
This type of conflict occurs when two developers use images that haven’t been used before in the storyboard. The images can be used in buttons, image views, or any other view that can display an image. References to the images get added to the Resources section in the storyboard’s XML and can cause a merge conflict, regardless of where the images are used in the storyboard (see Figure 3).
Fortunately a merge conflict in the Resources section is trivial to resolve – it’s very similar to a merge conflict when adding new files to an Xcode project file. Select one of the “both sides” options to merge the changes; the order does not matter.
Multiple New Scenes
This conflict can occur when two developers each add a new scene to a storyboard and the storyboard adds the new scenes to the same place in the XML. To resolve the conflict, examine it to be sure that the conflict is at the scene level as shown in Figure 4.
Examine the conflict on each side. If the conflicted XML is wrapped with a scene tag (including the scene tag) and appears to be in the same spot in the XML, the conflict likely falls into this category. More complex conflicts can be identified in the same way; if there are several scene elements on one or both sides, it’s possible that the developers each added multiple scenes to the storyboard. If there are only conflicts inside a scene element on either side, the conflict falls into the next category: “Editing the Same Scene”.
There are two approaches to resolve the conflict, depending on how the diff ends up. If the diff includes the entire scene on each side, then select one of the “both side” options to merge the changes; the order does not matter. If the scenes were somewhat similar, it is likely that the diff will look like the example in Figure 4, where some parts of the scene appear to be unchanged and other parts are conflicted. In this case, use a text editor to move the scene from one side below the scene from the other side, then remove all the conflict tags. Be consistent when removing the conflict tags for each scene – if the scene was on the right, remove only the left side of the conflict for that scene.
After merging but before committing the resolved conflict, be sure to open the storyboard to confirm the changes are correct and did not break the XML structure. Next, update the layout of the new scenes as they will possibly be piled on top of each other.
Editing the Same Scene
This type of conflict occurs when two developers make edits to the same scene. Sometimes developers can get away with editing the same scene simultaneously – the trick is to avoid touching the same objects in the scene, either directly or with constraints. When a conflict occurs, it is typically difficult to resolve manually as the XML is fairly complex. Not only will there be differences in the objects in the scene, but each of these objects will likely have constraints interacting with other objects in the scene. See Figure 5 for an example of this type of merge conflict.
The best approach with “Editing the Same Scene” type conflicts is to avoid them in the first place. Be careful when assigning tasks to developers to segregate them across scenes; if multiple tasks affect the same scene in a storyboard, see if the tasks can either be combined or can be assigned to the same developer to coordinate the changes and avoid a conflict.
One technique that can be especially helpful for large storyboards is storyboard references. Interface Builder has a storyboard reference object you can place in any storyboard, and it can represent a scene from any other storyboard (just select the storyboard and scene in the inspector for the storyboard reference). You can attach segues to a storyboard reference, so they function just like a scene in the same storyboard. Interface Builder also provides a tool to cut out one or more scenes to make a new storyboard, and replace the scenes with a storyboard reference (select Editor | Refactor to Storyboard… from the Xcode menu after selecting one or more scenes).
Another approach, when parts of a scene serve different functional needs, is to break parts of the scene out into child view controllers that can be embedded in a scene or parent view controller. This can cause a small increase in complexity managing any interactions between the child view controllers, but has the benefit of making them smaller units that can be edited by different developers.
When this conflict occurs, the quickest solution is to accept the changes for the most complex side, and drop the changes for the simpler side. Then merge the changes into the parent branch, and merge the changes from the parent branch back into the second, simpler side. At this point, the developer can re-apply the changes originally made and adjust as necessary to the change introduced from the more complex side, test, and merge again without a conflict.
Why not just merge the conflicted changes and hope for the best? When trying this approach in the past, I have frequently gotten odd behavior in the storyboard as a result of not getting the XML updated in a way that Interface Builder can handle. This odd behavior can be anything from an object not positioning correctly to an object not responding to edits as expected in Interface Builder. Odd and unexpected behavior can also be encountered when running the app; for example, a button might not recognize a tap as expected, or layout issues might occur that don’t make sense with the existing constraints. These types of issues can be frustrating and take a long time to investigate and resolve, especially when you don’t realize they are a result of attempting to resolve the merge conflict manually. In that case you will end up tearing apart the scene and rebuilding it in order to get it to work correctly anyway.
As you get more comfortable reading the source XML for storyboards and identifying the types of merge conflicts, you may encounter cases where multiple merge conflicts of different types have happened in an attempted merge. The techniques described can be used in combination to resolve multiple conflicts in a storyboard – just be careful to accurately identify each type of conflict or bad things can happen.
Have No Fear
Using the categories described here with some care and discipline in assigning tasks to developers, merge conflicts in storyboards can go from being catastrophic to being handled effectively. Don’t miss out on the benefits of storyboards in your project just to avoid some possible pain with merge conflicts – you can handle them!