2
votes

I'm writing an app that should present overlays in specific situations, like for example the lack of location services enabled for the app.

Overlay is a UIView with a UIImageView (background) a UILabel (title) and a UIButton calling a specific action. I want to use Interface Builder to set up the overlay UI but I would like to recall the overlay and show it on different UIViewControllers, depending on when the lack of location services is detected.

I have set up a custom class (subclass of UIView) to link a xib file. Code below:

class LaunchCustomScreen: UIView
{

   @IBOutlet var title: UILabel!
   @IBOutlet var enableLocationButton: UIButton!
   @IBOutlet var waitingIndicator: UIActivityIndicatorView!
   @IBOutlet var bckgroundImage: UIImageView!

   func setupDefault()
   {
       title.text = "Location Services Required"
       enableLocationButton.setTitle("Enable Location Services", forState: UIControlState.Normal)
       enableLocationButton.addTarget(self,
        action: "promptUserForLocation",
        forControlEvents: UIControlEvents.TouchUpInside)

       hideLocButton()
   }

   func hideLocButton()
   {
       enableLocationButton.hidden = true
   }

   func showLocButton()
   {
       enableLocationButton.hidden = false
   }
}

Then I have created the xib file which is of Class LaunchCustomScreen and I linked the IBOutlets to all the objects in it (UILabels, UIBUtton, UIImageView)

Then I have set some global functions to be called from any other UIViewController in order to show/hide the overlay on the specific view controller and configure it with UIButton hidden or visible (it will be hidden with a waiting indicator when user location is still loading). Below related code:

func setupLaunchDefault(vc: UIViewController) -> LaunchCustomScreen
{
    for aSubview in vc.view.subviews
    {
        if aSubview.isKindOfClass(LaunchCustomScreen)
        {
            NSLog("Found already a launch screen. Removing")
            aSubview.removeFromSuperview()
        }
    }

    var screen: LaunchCustomScreen = LaunchCustomScreen()
    screen.setupDefault()
    return screen
}

func showLaunchAskLocation(vc:UIViewController)
{
    var screen = setupLaunchDefault(vc)
    screen.bounds = vc.view.bounds
    screen.showLocButton()
    vc.view.addSubview(screen)
} 

Now I'm trying if the solution works and it crashes on the setupLaunchDefault function. Reason is that even if an instance of LaunchCustomSCreen is created, the variables (title, enableLocationButton) are still nil. I though they should be non-nil thanks to the IBOutlet to the xib... what am I missing?

Thank you in advance for your help!

1

1 Answers

2
votes

I have set up a custom class (subclass of UIView) to link a xib file

No, you haven't. No such "link" is possible.

what am I missing?

You're not missing anything, because you've already figured it out!

Merely creating a LaunchCustomScreen instance out of thin air (i.e. by saying LaunchCustomScreen(), as you are doing) merely creates an instance of this class. It has nothing whatever to do with the .xib (nib) file! There is no magic "link" whatever between the class and the nib! Thus, nothing happens that would cause these properties to get any value. They are, as you have rightly explained, nil.

You have designed and configured one special particular instance of LaunchCustomScreen in the nib. That is the instance whose outlets are hooked up, within the same nib. So if you want an instance of LaunchCustomScreen with hooked-up outlets, you must load the nib! Loading the nib is exactly equivalent to making an instance of what's in the nib - it is a form of instantiation. And here, it's the form of instantiation you want, because this instance is the instance you want.

So, the answer is: do not say LaunchCustomScreen() to get your LaunchCustomScreen instance (screen). Instead, load the nib to get your LaunchCustomScreen instance - and all will be well.

So, let's say your .xib file is called LaunchCustomScreen.xib. You would say:

let arr = NSBundle.mainBundle().loadNibNamed("LaunchCustomScreen", owner: nil, options: nil)
let screen = arr[0] as UIView

The first result, arr, is an array of top-level objects instantiated from the nib. The first of those objects (probably the only member of the array) is the view you are after! So you cast it to a UIView and you are ready to stick it into your interface. Since the view comes from the nib, its outlets are set, which is what you're after. You can do this as many times as you need to, to get as many "copies" of this view as you like.