Avoid hard coding storyboard IDs in 2 places - ios

When I want to set an ID for a View Controller I go into the storyboard file, select the ViewController, then on the Identity inspector I type in the Storyboard ID as something like "theVCID":
When I want to use that View Controller in code I do something like:
UIViewController *myVC = [myStoryboard instantiateViewControllerWithIdentifier:#"theVCID"];
Is there a way I can just hardcode this ID string in one location instead of hard coding it inside storyboard and when I use it in code?
If I could get a variable in the storyboard XML, I could just get the hardcoded string variable inside the XML source, but I don't know how/where to set up a variable so that the storyboard XML can access it.

Unfortunately you are forced to duplicate the declaration.
I wouldn't recommend parsing the XML as you will be basing that on the assumption of an implementation detail. Technically, storyboards could change their data type to anything in the future and this would break your implementation.
The safest way to do this is to keep your storyboard identifiers in a single class (say StoryBoardCoordinationController) and simply reference the storyboard objects through this single interface. Hopefully you should never be in the situation where these values are changing often, and if they are you should definitely seek to do something about it:)

Related

Can we access the objects in Storyboard by ObjectID

I have a bunch of static objects (UILabel, buttons, views) in multiple Scenes. They are not connected to any IBOutlet. But I'd like to access them at appdelegate (or first VC), and change their properties before it is loaded.
Anyway to do this?
EDIT: Adding my intention:
I actually wanted to make a custom "multi-language" app. I want to be able to change language from within the app. I can get a list of all the objects by applying built in localization of storyboard (Main.strings is autogenerated). Then I disable localization again. Then from this autogenerated file, I want to be able to connect it to a json data based on language that I select.
Of course you can. For example, you can use tags of UIView. Just set tags in Storyboard. It's easy but not so good. Another way to do this is using Accessibilities. Enable and set for it in Storyboard.
And then you can access it by accessibilityIdentifier property.
I will post my choice of "solution". So what I did was make use of accessibilityIdentifier to set the "key" for the multilanguage phrase translation purpose.
And I make use of the UIView+Recursion class (you can find this simple class somewhere in SO), and basically iterate all the objects in a particular Scene and if the text matches, set the key in accessibilityIdentifier property (either in viewDidload or viewWillAppear or viewDidlayoutSubviews).
This way you can have language changes "on-the-fly" within the app, without restarting.

Wrapping my mind around Objects in iOS

I'm having trouble understanding the idea of objects. From what I've read, they're instances of a class. When learning swift, they're quite easy to understand. Simply create a class and create an instance of it, and from there, you can modify it's properties and call its methods:
class ExampleClass {
let ExampleProperty = "rabbit"
}
let exampleInstance = ExampleClass()
But I don't see how that translates when using iOS, since I haven't seen any objects being created explicitly yet:
var example = Wss()
So my questions are:
Are things like buttons, labels, and sliders objects?
-If so, where's the "code" behind them? Why do buttons, labels, etc. display even before they're connected through outlets and actions to the View Controller? Is there a hidden "var thisButton = ThisViewController()" embedded into each of those sliders and buttons?
If my assumptions are wrong, can someone explain to me how objects work?
"Is there a hidden "var thisButton = ThisViewController()" embedded into each of those sliders and buttons?"
No, and this is exactly where interface builder excels. Much of Xcode's modern Interface Builder comes from NeXTSTEP. When you drag out a new UI component like NSButton and place it on your story board, Xcode is instantiating a new object of the NSButton class for you. When you save your file, Xcode serializes all the objects of your story board into a .nib file. At the time when this was invented, it was quite revolutionary, all made possible because of the dynamism of Objective C. It made GUI programming much simpler and dynamic. Every object in your story board is aware of its class. For example, when you instantiate a new NSButton, you can open the inspector and see for yourself that its class is NSButton. When you add custom views to your application, they keep track of their class in the same way. Whenever a nib file is loaded, these views are instantiated from their classes. You might have noticed that you never override the initializer of your views. Instead, you override methods like awakeFromNib. This is because there's a lot of behind the scenes work being done for you, from the time the object is first instantiated, to the time. During this time IBOutlets and IBActions are bound for you.
Competitors tried to make similar interface building applications, but they ultimately resorted to doing code generation behind the scenes. In these systems, when you saved your interface file, the program would generate a source file that contains code that instructs how to instantiate these objects anew whenever the interface is loaded. However, it proved significantly more complex a task then just serializing the objects, so these systems were error prone, and significantly harder to debug (because you'd be trying to debug machine generated source files).
Answering your questions:
Yes. Your objects are just being created from a NIB, or Storyboard. So the NIB, or Storyboard, will create those visual (UI) elements for you, which you can then be accessed via the IBOutlets
Your assumptions, are not completely wrong as in, there is in fact something allocating those objects for you. The NIB, or Storyboard, just describe a way for those objects to be created. Also some other customisations, like frames, colors, etc.
More about how this ties up can be found here.
Building on Alexander's answer:
UIView objects have a method init(frame:) that lets you create a new UIView object with a specified frame.
Other UIView subclasses might have init methods that take additional parameters.
UIView objects also support an init method init(coder:) that knows how to create an object from a stream of stored data. This is known as "deserializing" the object, or converting it from a byte-stream back into a running object.
When you build an object in a Storyboard or XIB file in Interface Builder, the system serializes the object into a byte stream and saves it into your Storyboard/XIB.
Then when you invoke the storyboard scene/XIB, the system reads the data stream and uses it to recreate (deserialize) the objects that are described in the storyboard/XIB.
The effect is essentially the same as if you wrote a bunch of code that created and configured all your views, but instead of writing all that code you are able to build your interface in Interface Builder, which is faster and easier to create, and MUCH faster and easier to update and maintain than a bunch of custom code.
But I don't see how that translates when using iOS, since I haven't seen any objects being created explicitly yet
There's no difference between the objects in iOS and what you understand objects to be. Objects are instances of a class. What you need to understand is that your own code is not the only place where objects can be created, and your own code will often interact with objects created outside your code. Here's a simple example:
let defaults = NSUserDefaults.standardUserDefaults()
Here defaults gets a reference to a user defaults object that the system provides. You never need to instantiate NSUserDefaults yourself.
Are things like buttons, labels, and sliders objects?
Yes, those are instances of UIButton, UILabel, and UISlider, respectively.
If so, where's the "code" behind them?
It's in the UIKit framework. You don't get to see the source code for those classes, but you can still use them by linking the framework into your app.
Why do buttons, labels, etc. display even before they're connected through outlets and actions to the View Controller?
You're talking about storyboards here. When you set up a view in Xcode's storyboard editor, the data that's stored in the storyboard file is essentially an archive containing serialized objects. When a view controller is instantiated from a storyboard, the objects in the storyboard are recreated from that data and then connected to the view controller's outlets. You can start this process yourself by instantiating a new view controller like this:
let storyboard = UIStoryboard(name: "MyStoryboard", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "MyViewController")
You don't usually need to do that, though, because the segues in your storyboard provide for transitioning between scenes, including creating the view controller that's the destination of a given segue.

why does instantiateViewControllerWithIdentifier need to use "as!"

let someVC = self.storyboard!.instantiateViewControllerWithIdentifier("something") as! SomethingViewController
why do we need to use "as! DataEntryViewController"
it works when I take it out using xcode 7.3.1
Define "works".
Yes, you can instantiate a view controller without caring which subclass of UIViewController it is. And the object you get back from that method will be a view controller, so it's safe to do things with it that can be done to all view controllers: present it modally, push it onto a navigation controller, add it to a tab controller, etc.
However, if you're going to do something with it that's specific to your view controller class -- like if SomethingViewController defines a property that lets you choose which Something it displays, and you want to assign to that property -- you need to do two things:
Test at runtime that the object you got back from instantiateViewControllerWithIdentifier is the class you expect it to be. (Because depending on the identifier you pass it could be some other class, or nothing at all if that identifier isn't in the storyboard.)
Let the compiler know that the variable you've assigned that object to is typed for that class, so that the compiler will let you access properties and call methods of that class.
Assignment with an as? or as! cast does both of those things in one step. (Whether to use as? or as! just depends on how much you trust yourself to make sure your storyboard identifiers are what they claim to be, and how you want to handle failure of such assumptions.)
In fact, even if you're not using properties or methods specific to that view controller class, that as! cast adds a runtime check that your view controller class is what you expect it to be. So the fact that nothing breaks when you take the cast out isn't a sign that the cast is superfluous — it's a sign that whatever breakage the cast was checking for is not currently happening.
That is, the line you quoted would lead to a crash if somebody changed your storyboard so that the identifier "something" was on a SomethingElseViewController. If you took that cast out, you wouldn't crash there. (And you'd probably run into trouble later.)
However, if what you really want to do is assert the validity of your storyboard and program, it might be better to be clear about that:
let someVC = self.storyboard!.instantiateViewControllerWithIdentifier("something")
assert(someVC is SomethingViewController)
// then do something non-SomethingViewController-specific with it

Input from UITextField connected from storyboard to Swift view controller file

Using Swift, how do I get the input from a UITextField?
In my Main.storyboard, I have chosen a "Text Field" from the premade components and dragged it onto my storyboard.
Now how can I access its value in my ViewController.swift?
What I'm currently doing:
I ctrl+drag the text field into my ViewController.swift file. Then in the popup-box I choose "outlet" and name it "myInput".
Then in another function, I access its value by using self.myInput.text.
Is this the only way to access its input? Is my approach following Swift conventions?
Yes, your approach of creating an IBOutlet from the storyboard and referencing it using self.myInput.text is a standard way of accessing this field in Swift.
As for whether to use self.myInput vs. myInput, I prefer to always write self because it is very obvious that the variable is a property and will probably be changed in many different places (opposed to a local var). Also, once you have self there, you don't have to worry about introducing local vars with the same name or making changes if you copy and paste a block of code into a closure that requires self.

Objective-C: what design patterns are there to hook up a model with views that are selected from a property list?

I am trying to build a MVC app with Objective-C. It is supposed to be a questionnaire app. I have all the questions and multiple choice answers stored into a property list, because I have different of questionnaires that I want to be able to load with this app. The idea is that the main model will keep track which item it should read of the property list, and select the corresponding view and viewController.
So schematically I have the following problem.
The RootView shows the start menu, that selects which questionnaire you will be able to take.
The RootViewController is the first controller called by the app delegate. It is supposed to instantiate the model and show the RootView. It furthermore controls the buttons of the RootView.
The model is supposed to wrap the items of the property list into a fitting datastructure, and supply it to the view controllers that need it.
The SelectedViewController is a controller that is a template specifically made for a type of question. The question could be a multiple choice, an open question, a 3, 5 or 7 choice likert scale kind of question, anything really. The template name that these view controllers will really get is ViewController.
The SelectedView is a tailor made view to the question type and will get the same name format as all the selected view controllers.
Here are my ideas.
My initial hunch is to use the delegate pattern, and set the model as a delegate to any SelectedViewController.
I could also use the delegate pattern to the RootViewController, and let him monitor if the SelectedViewController should be destroyed (via a delegate message). In that case, I can implement a prepareForSegue in the RootViewController to the SelectedViewController.
Since it is a questionnaire from a plist I could also add a prepare for segue to
every selected viewcontroller, but that will probably be a problem,
since there are at least 15 different ways of displaying the
questions.
Apparently there is also something like Key-Value Observing, according to this question. So that's also something I could use.
I think there is a definite way to deal with this, because the design patterns in iOS are pretty wel described, so there should be a few options for this really (or only just one). At the moment I am leaning towards setting the RootViewController as a delegate to the SelectedViewController and let the RootViewController handle the model. In this way I am extending the RootViewController to also hold all common functionality that every SelectedViewController should have.
But I am really not sure if this is the way to go, because my knowledge on design patterns is limited. My question is: what is the right option to choice in this specific situation (e.g. views and view controllers selected via a .plist file)?
There is no need for a specific pattern - you can deal with accessing an instance of a model object by name, i.e. in the same exact way that you deal with making a specific view and the view controller.
Let's say you are looking to connect the QuizQuestionViewController4MC and its QuizQuestionView4MC to their model. Let's assume that the model class is called QuizQuestionModel4MC, and that it needs to be configured with an object that you get from a key #"4MC" in the plist. Since your code learns the name of the model class only at runtime, you can create an instance using reflection:
NSDictionary *dataFromPlist = ... // Someone passes this to you
NSString *identifier = dataFromPlist[#"identifier"]; // Returns #"4MC"
NSString *ctrlName = [NSString stringWithFormat:#"QuestionViewController%#", identifier];
NSString *modelClassName = [NSString stringWithFormat:#"QuizQuestionModel%#", identifier];
id model = [[NSClassFromString(modelClassName) alloc] init];
// Configure the model with the data from plist
[model setPlistData:dataFromPlist];
// The model is ready to be used - give it to the view controller
MyBaseViewController *ctrl = [storyboard – instantiateViewControllerWithIdentifier:ctrlName];
// Give the view controller its model - now it is ready to be used
[ctrl setModel:model];
Note the class of the view controller - MyBaseViewController. This is not your root view controller, it's a base class for all your specific view controllers. It is this view controller that knows about a model, but it does not know the specific subclass in the model hierarchy. Each subclass of the view controller knows about its specific model subclass, so it can access the information from the model class directly, without going through selectors or KVP.
Of course it is up to the designer of the app to "wire up" correct view controllers to the correct models. In terms of the above example, QuizQuestionViewController4MC needs to know the structure of the QuizQuestionModel4MC in order to avoid sending unrecognized selectors to an incorrect class.

Resources