قالب وردپرس درنا توس
Home / IOS Development / Faux Dependency Injection for Storyboards

Faux Dependency Injection for Storyboards



I do a lot of work where I need to set up views and see controls for a large number of monitors and I have to admit that I enjoy using Storyboards for the most part. I know it's a polarizing topic with iOS developers, and there are many specific cases where Storyboards do not work or work poorly, but for the most part, the process of using Storyboards is hassle-free. I assign a large part of this to make as much setup as possible in Storyboards and keep configuration in code either as initialization shutdowns or in object subclasses.

But my biggest problem with Storyboards is the lack of addiction injection. If you develop views entirely in code, you can customize your initialization methods to take necessary (or optional) parameters that affect loading and viewing of the viewer.

Consider a profile screen showing user profile image, username, email, etc. We could load our current user from our session singleton and configure the views in viewDidLoad and instantly configure our views with the user's data. However, if we want to share this screen to show profiles from other users, we need to do some extra work by implementing some increased singleton method to get the data of the other users. It does not feel nice and definitely not very swifty.

Instead, it would be better to send the user object that we would like to display to our profile viewer on initialization and then be able to use the data in viewDidLoad to enter detailed data in our views. If you are building your app completely in code, this is easy to accomplish because you can create the initialization method of your display control and use the specific method when you want to slide your profile on the screen:

  class    ProfileViewController  :    UIViewController    {
      var    profileUser:    User 

      init  (user:    User)    {
          profileUser [19659008] = [19659008] uses 

          super    init  (nibName:    nil     bundle:    nil ). 
    } 
    
    ]    init ?    aDecoder:    NSCoder)    {
          fatalError (  "init has not been implemented" ) 
    } 
} 

  class    MainViewController :    UIViewController    {
      @IBAction    FUNC    profile  ()    {
          la [19659008] currentUser    =    User (name:    "Nick"     userpicURL:    NSURL (string:    "https://thatthinginswift.com/profile .png" )) 

          la    profile    =    ProfileViewController (user:    currentUser) 
          navigationController .pushViewController (profile,    animated:    true ) 
] 
] 

We do not control the call to ProfileViewController its init method ( initWithCoder actually) when using a Storyboard so we are out of luck they r. I've used prepareForSegue the method of adding data that the upcoming display controller needs to do the job, albeit with too much boilerplate code to my liking. This is a good overview of this method at Natasha Robot last week, here is the short example:

  override    func    prepareForSegue  (Segue:    UIStoryboardSegue,    Sends: 19659008] AnyObject?)    {
     ]    segue.identifier    ==    "profile"    {
          la    currentUser    =    User    "Nick"     userpicURL:    NSURL (string:    "https://thatthinginswift.com/profile.png" )) 

          la [19659008] dest    =    segue.destinationViewController    as !    ProfileViewController 
          dest.profileUser    =    currentUser 
    } 
} 

prepareForSegue is really the only option to set these values ​​before the new display control takes over, so any solution we will make goes around prepareForSegue . Fortunately, before viewDidLoad for the new display control and viewDidLoad is usually the first time you take control of a display controller then (mostly) you can behave as if ] has always existed since initialization.


The first solution I encountered is a UIViewController subclass I call PreparedViewController and it overrides prepareForSegue reflects the properties of the current display controller and segue destination regulator and Automatically copies values ​​that have a given prefix. There is little so we can only display the code and usage:

  class    PreparedViewController :    UIViewController    {
      overstyr    func    prepareForSegue     ]    dest    =    segue.destinationViewController 

          // get some destination properties with our prefix 
          la    prepProps    =    Mirror (reflects:    dest) .children.filter    {   {$ 0.label    ??    ""             ] )          19659008] for    prop    in    prepProps    {
              // looking for a property At the current display control with the same name 
              la    selfProps    =    Mirror (reflects:    itself ). children.filter    {   {$ 0. label    ??    "" )    == [19659008] prop.label  } 
              // package out all and seen via KVC 
              if    la    sameProp    =    selfProps.first,    childObject    childObject    =    sameProp.value [19659000] as     AnyObject,    label    =    prop.label    {
                  dest.setValue (childObject,    Forkey:    label) 
            } 

  
    } 
}    class    ViewController :    PreparedViewController    {
         prepCtxFloat    =    40.5 

] regularFloat = 20.1
}

// victory between ViewController and SecondViewController set in Storyboard

class SecondViewController : UIViewController {[19659010] var prepCtxFloat: Float = 0
var regularFloat :? Float

Board func viewDidLoa {
// prepCtxFloat is now 40.5
}
}

In this case SecondViewController prepCtxFloat will be set to 40.5 automatically under the story because the property name matches regularFloat will not move between ViewController and SecondViewController because it does not have the necessary prepCtx prefix.

This approach uses key value coding to accomplish the task which is not just a problem. UIViewController is anyway a NSObject subclass, but it's crazy if the types do not match. You can add a little stricter controls to prevent crashes, it is information that Mirror will provide, but there is no way to get compiled information about which transitions should work.

The other gotcha is that the recipient display controller's properties must be var and must have an initial value (ie they may not be optional because it is not compatible with Objective-C and thus KVC, you get one This class is not key coding compatible error if you try this with optional). Perhaps not a big deal for simple values, but you will soon be frightened to make big dummy objects just to be held prepareForSegue replaces them. Default values ​​in a world where options are the real solution, I'm also wrong in the wrong way.

The other solution I came up with is almost not worth mentioning: a protocol that requires a common segueContext dictionary in each viewer and sync them under prepareForSegue . Protocol-ness means that you still need to call syncContext or something under the guise. And I'm not sure I want to go back to a world where I have to remember which type is which one when you unpack all these magic strings from a dictionary. All the complexity of a JSON parser, but available to you when you perform a thrill!


This is the second time I've started an implementation that could be greatly improved by something like native Swift KVC Support. It's a bit unfortunate - but understandable - that some KVC-like has been pushed for post-3.0 work; we just have to wait a little longer before these types of tools can be seamless.


Questions or comments? Find us at twitter or send a problem on github




Source link