Different from foreign countries, StoryBoard has been questioned by domestic developers since its launch for many reasons, such as bad for multi-user collaboration, hidden UI details, difficult testing when problems happen, and low execution efficiency. This article is aimed at these problems for example and analysis.

StoryBoardXibWhat’s the difference?

Both StoryBoard and Xib are view serialization tools for separating UI style code, improving view code reuse, increasing WYSIWYG, reducing the complexity of view tests,

  1. Among themXibIn order to viewViewGive priority to,
  2. StoryBoardWith the controllerControllerAnd the relationship between them, and the viewViewThe relationship between.

Actual use examples to see the pure Swift project – Xib | StoryBoard equipment adaptation skills “or other StoryBoard

StoryBoardXibIt’s not conducive to teamwork,gitMerge code is conflicted and hard to handle?

This is the biggest reason to denigrate StoryBoard, and it seems the strongest. The most striking example is the one below.

In a Storyboard, a lot of controllers and segues show up with intricate UI relationships that can be daunting or difficult to maintain.

But it shouldn’t beStoryboardThe pot is just an abuse of tools by users!

Yes, abuse, whether it’s storyboards, whether it’s pure code, they’re all tools at heart. Tools are not good or evil. Even in pure code development, if there is no naming convention, arbitrary nesting of ifs, no adherence to MVC or MVVM development patterns, and no differentiation between development and production environments, how can the code be maintainable and collaborate with multiple people?

So how to use it in reverseStoryboardNot abuse?

The best way to avoid abuse is to customize specifications, just like many specifications in your code. Each team may have their own preferences, so I’m going to list out the Storyboard specifications for our team for your reference.

Each module is independentStoryboard eachStoryboardThere should only be one main VC and child VC on the same page, and there should not be more than two main VC
  1. In one project,StoryboardIt shouldn’t be isolated. It should be likeMVPPattern like, each page has its ownStoryboard, eachStoryboardThere should be only one LordVCAnd the children on the same pageVC, the mainVCThere should be no more than two. (In most cases, oneStoryboardThere should only be oneVC)
  2. Between the pages ofSegueThe connection should be usedStroyboard Reference Scene.UITabBarControllerBecause of complexity, the child page should be treated as the masterVCdisposal
  3. The initial style of the view should be set as much as possible in the properties pane on the Storyboard, and unless very special, the layout should be done in the Storyboard using various constraints. This helps separate the view style from the view code and improves the reuse and compatibility of the view code.
  4. For VC with complex logic, Object objects should be added and corresponding classes bound to separate logic codes.
  5. For rounded corners, background colors, shadows, etcCALayerThe style should be extended or subclassed in the form of instances used@IBInspectableProperty keyword, inStoryboardThe initial style is set in the properties panel.
  6. For custom views, use@IBDesignableThe keyword protection is inStoryboardWhat you see is what you get!

Using the above principles, as long as the task division is reasonable, it is almost impossible for many people to modify the same Storyboard at the same time. Even if coordination errors happen occasionally, the amount of code in a simplified Storyboard is not large, and it is easy to handle Git conflicts with the help of file comparison tools.

At the end of the day, bloated storyboards are just as difficult to maintain and git prone as bloated viewcontrollers. The only solution is to use tools in moderation.

StoryBoardXibHidden UI details and easy to causeViewControllerBloated?

Rather than hiding UI details from storyboards and XiBs, Apple wants to use them to guide developers to properly use views and controllers when creating view instances

    required init? (coder aDecoder:NSCoder) {}Copy the code

The constructor creates the view instance. All initial styles are values set in the properties pane, passed

    func setValue(_value: Any? , forUndefinedKey key: String){... }Copy the code

To assign the corresponding property to the view.

As far as ViewController bloat is concerned, it’s ridiculous. StoryBoard provides multiple ways to separate code, but many people don’t know that.

Take meituan’s home page UI for example

Such a home page is quite complex and requires multiple CollectionViews and a UITableView for normal layout

If the Delegate of these views is implemented by the ViewController, it will be bloated and messy.

Normally, handwriting would have three Child ViewControllers to solve the bloat problem, but couldn’t storyboards do that?

The answer is no, as apple came up with the solution shown above early on. A placeholder container view points to the child controller’s Embed Segue

Hold down the Control key to wire to the child controller you want to include, placeholder view instance == View of the child controller (root view of the child controller)

After selecting the Embed mode, the size of the child controller changes to the same size as the placeholder view

So we can put the code for the CollectionView of the function icon on the first child controller, and the CollectionViewDelegate, CollectionViewDataSource, and so on are implemented by the child controller

Similarly, you can add a Container View to the preferences area, pointing to a second child controller.

throughContainer ViewTo create theChildViewControllerHow to masterViewControllerPass parameters or call each other?

ChildViewController by self. The parent (Swift) | | self. ParentViewController (OC) to get the ViewController instance. Main viewcontrollers can through self. Chilren (Swift) | | self. ChildViewControllers (OC) to get ChildViewController instance, it is an array, The order is equivalent to the order in the placeholder view hierarchy.

It’s worth noting that ChildViewController created this way is constructed later than the primary ViewController, but viewDidLoad in its life cycle precedes the primary ViewController, So in the viewDidLoad method in ChildViewController, self.parent is nil, and you can’t get the primary ViewController instance. If you want to get an instance of the primary ViewController at initialization, you should call a specific method of ChildViewController in the primary ViewController viewDidLoad method, passing self as an argument.


  • You can also use Object objects

Add it to the controller.

It’s essentially a subclass that inherits from NSObject, and we can think of it as a controller for a small functional module.

class FeaturesController: NSObject.UICollectionViewDataSource.UICollectionViewDelegate {
    
    @IBOutlet weak var collectionView:UICollectionView!
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        <#code#>
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        <#code#>
    }
}
Copy the code

Select this Object in the Storyboard and bind the class

Object

Right-click the CollectionView and set the Delegate and DataSource connections

Call this module’s methods or pass arguments in the main ViewController

class HomeController: UIViewController {
    
    @IBOutlet weak var featuresController:FeaturesController!
    
    override func viewDidLoad(a) {
        super.viewDidLoad() featuresController.datas = [....]  featuresController.collectionView.reloadData() } }Copy the code

Similarly, if a page needs more than one child module, you can drag multiple objects into a Storyboard and bind different module control classes, as opposed to placeholder Container View and ChildViewController methods. The Object method is much simpler in passing arguments or calling each other. The disadvantage is that there is no ChildViewController lifecycle method. If you want to use viewWillAppear, you need to call the Object custom method in viewWillAppear of the main ViewController.

It’s not the Storyboard that makes the ViewController bloated, it’s the design that makes the ViewController bloated, even if you don’t use the Storyboard and put everything in one ViewController. This is all up to the user, not the Storyboard!

StoryBoardXibIt’s not easy to test if something goes wrong, right?

In fact, this question is very vague, and I have consulted many people before I know that the so-called question is not easy to test, which refers to the following two situations:

  1. Modify or delete@IBOutletOf the variable name corresponding toStoryboardFailed to do processing, resulting in runtime crash, crash content can not read!
  2. When the class name of the binding changes, the correspondingStoryboardFailed to do processing, resulting in runtime crash, crash content can not read!

It’s actually pretty easy to see how Apple parses the XML in Storyboard into a view, so the error that crashed is actually the view construction method that I mentioned earlier, right

    required init? (coder aDecoder:NSCoder) {}Copy the code

If the bound class name changes, output error:

  1. Unknown class _TtC11ProjectName14HomeController in Interface Builder file. // Swift
  2. Unknown class HomeController in Interface Builder file. // Objective C

Interface Builder file means to build a view or controller from a Storyboard or from a Xib, but you can’t find a controller named HomeController. We have a controller named HomeController bound to one of our storyboards, but we can’t find it in the code, maybe we changed it or deleted it. You can do a global search

This class is defined in the test. swift file, but cannot be found because of its name change.

Can this problem be avoided without a Storyboard? The answer is no, because when refactoring code, there are plenty of examples where a change was made and it was ignored. This applies even to pure code, so if you need to change a class name or variable name, you should take advantage of Xcode’s refactoring rather than simply changing it directly.

If you change the class name or the variable name, the bindings or wires in the Storyboard or Xib will change synchronously. You can’t make a mistake.

Similarly, the property of the @ibOutlet wire assigns a value to the view using the following method

    func setValue(_value: Any? , forUndefinedKey key: String){... }Copy the code

If the variable name changes, the following error occurs:

  1. *** Terminating app due to uncaught exception ‘NSUnknownKeyException’, reason: ‘[<HomeController 0x7fbd0ce20c40> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key featuresController.’

This method will throw an exception if it doesn’t find the featuresController property. A global search shows that the code has changed its name.

The solution is also to use refactoring when deleting the corresponding line or changing the variable name

Thus, the fact that it is not easy to test is due to careless refactoring and a lack of understanding of the construction process, otherwise problems would be easy to locate and fix. And if you use Xcode’s refactoring feature when refactoring code, you don’t even have a problem

StoryBoardXibReduced execution efficiency?

Storyboards and XiBs are essentially XML and need to be deserialized to parse into views, which is certainly not as fast as direct code creation, but that’s just a feeling. How much of an impact does it actually have? Let’s test it out:

        var controllers:[ViewController] = []
        let count = 30000
        controllers.reserveCapacity(count)
        guard let sb = storyboard else { return }

        var beginTime = CACurrentMediaTime(a)for _ in 0..<count {
            let vc = sb.instantiateViewController(withIdentifier: "ViewController") as! ViewController

            controllers.append(vc)
        }
        print("The Storyboard to create\ [count)When".CACurrentMediaTime() - beginTime)
        controllers.removeAll(keepingCapacity: true)
        beginTime = CACurrentMediaTime(a)for _ in 0..<count {
            
            let vc = ViewController()

            controllers.append(vc)
        }
        print("Pure code creation\ [count)When".CACurrentMediaTime() - beginTime)
Copy the code

The first time it was used 30,000 times, the result was output

  1. The Storyboard was created 30000 times in 8.648092089919373
  2. Pure code created 30000 times 27.226440161000937

What do we see? Is it faster to create from Storyboard than pure code? I couldn’t believe my eyes, and there must be something magical happening with such a big gap. In order to verify my idea, I copied the Storyboard creation again

        var controllers:[ViewController] = []
        let count = 30000
        controllers.reserveCapacity(count)
        guard let sb = storyboard else { return }

        var beginTime = CACurrentMediaTime(a)for _ in 0..<count {
            let vc = sb.instantiateViewController(withIdentifier: "ViewController") as! ViewController

            controllers.append(vc)
        }
        print("The Storyboard to create\ [count)When".CACurrentMediaTime() - beginTime)

        controllers = []
        controllers.reserveCapacity(count)

        beginTime = CACurrentMediaTime(a)for _ in 0..<count {
            
            let vc = ViewController()

            controllers.append(vc)
        }
        print("Pure code creation\ [count)When".CACurrentMediaTime() - beginTime)
        
        controllers = []
        controllers.reserveCapacity(count)
        
        beginTime = CACurrentMediaTime(a)for _ in 0..<count {
            let vc = sb.instantiateViewController(withIdentifier: "ViewController") as! ViewController

            controllers.append(vc)
        }
        print("The Storyboard to create\ [count)When".CACurrentMediaTime() - beginTime)
Copy the code

The output is as follows, and the results are similar across multiple runs, probably because the computer’s performance deteriorates as memory usage increases, but in any case, a large test empty ViewController is actually faster than pure code creation in this case.

  1. The Storyboard was created 30000 times at 8.513293381780386
  2. Pure code created 30000 times 27.19225306995213
  3. Storyboard created 30000 times 25.9916725079529

It’s a hazard to guess how this happens, but it’s probably because Apple has a cache override mechanism in Storyboard to make things more efficient when objects are created multiple times, whereas pure code doesn’t have that optimization. To test the guess, we gradually reduced the order of magnitude.

  1. 3000 Storyboard creation times 0.20833597797900438
  2. 3000 pure code creation times 0.2654381438624114
  3. 3000 Storyboard creation times 0.34943647705949843
  1. Storyboard created 300 times 0.010981905972585082
  2. 300 pure code creation times 0.005475352052599192
  3. Storyboard created 300 times 0.014193600043654442
  1. Storyboard created 30 times 0.0016030301339924335
  2. Pure code was created 30 times in 0.00031192018650472164
  3. Storyboard created 30 times 0.001034758985042572
  1. Storyboard is created 10 times in 0.0009886820334941149
  2. Pure code created 10 times in 0.0001325791236013174
  3. The Storyboard was created 10 times

As the number of times decreases, the Storyboard creation speed is gradually lower than that of the saved code, but it still takes less than 1/10,000th of a second to create a Storyboard. This efficiency is not noticeable to users, and repeated creation has advantages over pure code. Therefore, This is not a disadvantage of storyboards and XIbs either

inStoryBoardXibDrag and set constraint layouts are hard to pinpoint? Not easy to modify?

I think this kind of comment may be caused by not being familiar with the function and operation of Interface Builder, so I gave up after a few experiments.

Constraint layout is actually a very powerful function, can solve the problem of the vast majority (98%) layout adaptation, this number is not literally give 98%, a lot of people think amounted to less than the proportion because understanding constraint is less, or in accordance with the previous autolayoutMask use constraints, so a lot of layout problems still use code calculation, In fact, the constraint function is very powerful. Currently, it cannot be solved directly through the constraint, and the problems that must be assisted by code are very small.

But in contrast, there are many concepts of constraints, which are easy to be omitted depending on human brain thinking, so that various errors or anomalies will be reported when running. Therefore, it is very common to write constraints in pure code and repeatedly run the style and size of debugging view, and some pages are deep, which is very troublesome to test.

However, when using StoryBoard or Xib, there will be an error message due to lack of constraints or conflict of constraints. For different devices, you can switch tests directly on Interface Builder, which is much more efficient and accurate

If you need to learn more about using constraint on or StoryBoard Xib skill, can refer to the article “pure Swift project – Xib | StoryBoard equipment adaptation skills” and “pure Swift project – Xib | StoryBoard constraints, using skills” or other related articles.

Summing up,StoryBoardXibAlthough not without shortcomings, but far more than pay advantages, worth learning and research!