How to ensure a UIView has loaded? - ios

This may sound silly, but read on...
I want to set the text of a UILabel from outside of a UIViewController that is instantiated by a storyboard. I need to make sure that the label property of the view controller is set when I set its text otherwise the label's text won't be set(because it won't be loaded yet to receive a text value).
Here's my current solution:
// Show pin entry
if (!self.pinViewController) {
// Load pin view controller
self.pinViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"pinScreen"];
self.pinViewController.delegate = self;
if (!self.pinViewController.view) {
// Wait for pin screen to fully load
}
[self.pinViewController setMessageText:#"Set a pin for this device"];
}
Initially I had a while loop that looped until the value of view was not nil, But it seems the very act of checking the view loads it(as mentioned here: http://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewController_Class/Reference/Reference.html#//apple_ref/doc/uid/TP40006926-CH3-SW37)
I tried using the isViewLoaded method with no success. It just looped forever.
I've gone forward with the above code as my current solution, but it feels wrong.
Is there a better way ensure a UIView has loaded?

I want to propose an alternative way where you don't have to rely on the availability of the view.
If you need to wait for the view to load before you can call other methods on your viewController you break encapsulation, because the viewController that calls your PinViewController has to know about the inner workings of your PinViewController. That's usually not a good idea.
But you could save objects like NSStrings in the PinViewController instance, and when the view of the PinViewController will appear you set its views according to the properties you have set before.
If you need to change the text of an label from outside your viewController you can also create a custom setter that sets the label.text for you.
Your .h
#interface PinViewController : UIViewController
#property (copy, nonatomic) NSString *messageText;
// ...
#end
And your .m
#implementation PinViewController
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.messageLabel.text = self.messageText;
}
// optional, if you want to change the message text from another viewController:
- (void)setMessageText:(NSString *)messageText {
_messageText = messageText;
self.messageLabel.text = messageText;
}
// ...
#end

viewDidLoad should solve this I guess.
http://developer.apple.com/library/ios/#documentation/UIKit/Reference/UIViewController_Class/Reference/Reference.html

I would rather see you change your logic and do it the way that #MatthiasBauch shows in his answer. However, to answer your actual question, you can simply set a view property in order to force it to load:
self.pinViewController.view.hidden = NO;

Related

ObjectiveC - [self.view viewWithTag] returning null

I am completely stumped and have been researching for days. Probably something really simple that I am missing.
I have a ViewController which contains a custom UIView called GameView, and a UIView called buttonBox which contains a "next level" button. What I am trying to achieve is when the level is completed in GameView, it fires a function in my ViewController which shows the buttonBox so the user can click the "next level" button. It simply will not work.
I have attempted this in 3 ways, neither have worked:
Creating an IBOutlet in the ViewController, connecting it to the hidden UIView (and it was definitely connected) and calling setHidden:NO.
Calling the [self.view viewWithTag:xxx] and then calling setHidden:NO.
Using hidden=NO instead of setHidden:NO.
Relevant code for ViewController as follows:
#interface PlayViewController : UIViewController
#property GameView *gv;
#property (strong, nonatomic) IBOutlet UIView *buttonBox;
-(void) showButtonBox;
#end
#implementation PlayViewController
#synthesize buttonBox;
...
- (IBAction)showButtonBox {
UIView *uiv = (UIView*) [self.view viewWithTag:999];
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"Showing box function");
NSLog(#"%#", uiv);
uiv.hidden = NO;
});
}
#end
And my custom view:
#implementation GameView
...
dispatch_async(bgQueue, ^{
_loopRunning = true;
//NSLog(#"Calling main loop...");
while ([self loopRunning])
{
...
PlayViewController * pvc = [[PlayViewController alloc]init];
[pvc showButtonBox];
...
}
#end
The thing is, the variable uiv is returning null in NSLog, which is obviously why hidden is not working, but I have no idea why. It also didn't work when I was using IBOutlet.
Also, current output from NSLog is as follows:
2015-11-24 00:18:38.612 ib[12579:1264539] Showing box function
2015-11-24 00:18:38.612 ib[12579:1264539] (null)
Thanks in advance.
Correct Answer:
The problem was that I was using StoryBuilder to build my UI, but by using the alloc init method was creating a new view controller (which is never shown) instead of correctly referencing the view controller which was being displayed. This is achieved by passing the view controller being displayed to the view in the viewDidLoad function, see below:
#implementation PlayViewController
#synthesize buttonBox;
#synthesize gv;
- (void)viewDidLoad
{
[super viewDidLoad];
gv = [self.view viewWithTag:777];
[gv setPlayViewController:self];
}
...
Man, it's simple. Let's take a look at:
#implementation GameView
...
dispatch_async(bgQueue, ^{
_loopRunning = true;
//NSLog(#"Calling main loop...");
while ([self loopRunning])
{
...
PlayViewController * pvc = [[PlayViewController alloc]init];
[pvc showButtonBox];
...
}
#end
Here we have the issue:
dispatch_async(bgQueue, ^{
I assume, bgQueue stands for "background queue", which means this is not served by the main thread (the UI thread).
Having that said, it's quite naive to expect
[pvc showButtonBox];
to work properly. Just move this code into the main thread. For instance, you can just wrap the aforementioned line of code into a dispatch_async on the main queue. That should solve your probem, if your outlets and/or tags are OK. Cheers.
[[PlayViewController alloc]init];
This creates a new instance of PlayViewController. Where have you defined your outlets and views?
In a storyboard? You can't use this initialiser - nothing from the storyboard will be picked up, you have to use a segue or initializeViewControllerWithIdentifier:.
In a xib file? Is it called PlayViewController.xib? If not, it won't be picked up by the initialiser. Plain alloc/init of a view controller will only find a nib file as described in the documentation of the nibName property.
Do you really want alloc / init at all? Do you actually want to make a new view controller, or is one already on the screen?
From your comments it seems option 3 is the right answer. The PlayViewController is already on the screen, alloc/init is creating a new instance of it, which is never being put on screen, which never loads any views regardless of storyboards or nibs.
You need to get a reference to the existing instance of PlayViewController. Without knowing the structure of your app it's not too easy to say how that's done - is it presenting the game view? Is the game view a subview of the view controller's view? You may need to pass in a reference (weak) to the game view when it is created, at viewDidLoad, or set up an outlet in the storyboard.

Executing code/triggering UI Elements between two viewcontrollers

I'm new to objective C and design patterns like MVC, protocols and so on but this is it:
I am trying to write an iOS app within two viewcontrollers: the first has a textview where the user can write into, and the second has a UISwitch that triggers on "Value changed" and saves a file.
If I toggle by hand the switch on the SecondViewController it will save the file and that's ok.
But I wish the file could be saved from the FirstView just when the user types a specific word, it auto-switches to the second view, and auto-activates the UIswitch and all the method already behind it.
I still can't get the two interfaces working this way. Thanks everybody in advance for helping. Cheers!
this is connected in SecondViewController.h in the storyboard
-(IBAction)toggleFileSave:(id)sender;
and it is implemented as usual...
#interface SecondViewController ()
#property (nonatomic,weak) IBOutlet UISwitch *mySaveFileSwitch;
#end
- (void) toggleFileSave:(id)sender {
// how do I execute this code when the user
// type a specific word in the first view??
}
Create a BOOL flag in your SecondViewController.
Set it when the specific word is typed and push the view controller.
In the viewDidLoad of SecondViewController check the flag condition.If it is set call the required method.
When the specific word is typed:
ViewController2 *viewController = [ViewController2 alloc]init];
viewController2.flag = YES;
[self.navigationController pushViewController:viewController2 animated:YES];
In your text field delegate (add one if it doesn't exist) add this method:
- (void)textFieldDidEndEditing:(UITextField *)textField {
/* at this point the user finished editing */
NSString *currentText = /* read text field value */
if ([currentText isEqualToString:/* the magic word */]) {
/* save the file, present a view controller, etc. */
}
}
Check UITextFieldDelegate to know the available methods, you may need more than one to get the desired behaviour.
If you want to load the second view controller in order to show the UI and the save the file you can do as サンディープ said in his or her answer:
SecondViewController *controller = [SecondViewController new]; /* init as usual */
controller.saveOnLoad = YES;
[self.navigationController pushViewController:controller animated:YES];
Then, in SecondViewController:
- (void)viewDidLoad {
if (self.saveOnLoad) {
/* save file in async block */
/* set switch on */
}
}
If you don't need to show the second view I'd move the saving functionality to its own class and use it from the first controller, showing just a confirmation message for instance.

Changing label color of a label in a different view (delegates?) [Spritebuilder]

I have two scenes that I made in SpriteBuilder, one is Shop and the other is UpgradesNew. Shop is a CCNode layer and UpgradesNew is a CCNode layer. I have two CCScrollViews in the MainScene that load Shop and UpgradesNew respectively.
When one button in Shop is tapped, the label in UpgradesNew should change colors. I have been trying to implement this using delegates but it's not working.
Here's what I did...
In shop.h I set the protocol:
#protocol changeColorProtocol <NSObject>
#required
-(void)changeColor;
#end
Then I set the id
#property (nonatomic, retain) id <changeColorProtocol> delegate;
Here is the button that when clicked, should use changeColor. This is in shop.m
-(void) buyDiggerShibe {
[self.delegate changeColor];
[self didLoadFromCCB];
}
Now in UpgradesNew.h I made it adopt the protocol like this
#interface UpgradesNew : CCNode <changeColorProtocol>
And in UpgradesNew.m
I set delegate to self in ViewDidLoad.
Shop *shop = [[Shop alloc]init];
shop.delegate = self;
.
-(void)changeColor {
if (hasDigger == YES) {
shovelRequires.color = [CCColor greenColor];
NSLog(#"HEY HEY HEY");
}
}
I probably have parts of the delegate placed in the wrong area because I was trying to switch them around when it wasn't working, I'm not sure where they are supposed to go. I've watched multiple delegate tutorials and it just seems overly complicated, at least with what I am trying to do.
Any ideas?
EDIT:
Tried this.
I created a property in UpgradesNew
#property (strong, nonatomic) Shop *shop;
Then I synthesized it in the implementation and allocated it like this in didLoadFromCCB, instead of creating a new object:
self.shop = [[Shop alloc]init];
shop.delegate = self;
EDIT: This is how I am creating objects.
Drag a label into a layer. Identify it then define it in header as CCLabelTTF *label; That's it, thats all I do to create any object on the layer.
To create a layer like Shop or UpgradesNew, I hit New -> File -> Layer. That creates a new CCNode. Then I set the class of the CCNode, as shown in the picture the CCNode that is highlighted has a class of MainScene. If I want to establish a #property to that CCNode I just type the name in the box right below custom class and set it as doc root var, and then put it in the header as CCNode *MainScene. I don't do anything other than that.
I don't know anything about SpriteBuilder, so it's a bit hard to address your question. You might want to add SpriteBuilder to the title of your post so people who use that framework are likely to read it.
You need to explain how the 2 "CCNode layer"s are created, and how you link them together. In order for one object to have another object as a delegate, the delegate property has to be set somewhere. Where is that setup being done? Have you set a breakpoint at the line
[self.delegate changeColor];
To make sure that self.delegate is not nil?
Have you set a breakpoint in your changeColor method, or added a log statement, to see if it's being called? My guess is that self.delegate is nil, so the messages is being dropped on the floor (it's legal to send messages to nil in Objective-C. It just doesn't do anything.)

Where to set a property in UIViewController

A rather basic question I'm unsure about. I typically set up my UIViewController's view-related code in viewDidLoad. If the controller has some properties for labels, etc, this is where I would initialize them and add them to the view. These properties are usually declared in the .m so can be considered pseudo-private.
However - if the controller exposes one of these properties (let's say a UILabel) in its header file, I am finding that I can't rely on it existing when it comes time to set it up. For example:
CustomViewController *controller = [CustomViewController alloc] initWithNibName:nil bundle:nil];
controller.someLabel.text = #"label text goes here";
//then comes the presentation code
I find that I am setting the label's text too early - viewDidLoad has not fired yet so the label is nil.
Should I create this label in init and add it in viewDidLoad? Should I be doing all my set up in init? Or maybe all the initialization of view properties? Or judge it on a case by case basis?
Or maybe the root cause is that I shouldn't have a controller exposing a view (the label) and use some other pattern?
I'm looking for a consistent way to structure my code.
Yeah, you are pretty much right already. The thing is, all views components of your controller are not loaded until the view is actually presented. So you cannot set anything of your IBOutlets from outside the controller.
One approach for passing, for example, a text for an UILabel, it's create a new string property, let's say self.myString, assign it from outside, and in your viewDidLoad, set in the labels' text this property.
CustomViewController *controller = [CustomViewController alloc] initWithNibName:nil bundle:nil];
controller.myString = #"label text goes here";
And inside the CustomViewController:
- (void)viewDidLoad
{
[super viewDidLoad];
(...)
self.label.text = self.myString;
}
I tend to do something like the following, which works for me if I only want to update the view on demand (if I want to update it more frequently then I would do so in viewWillAppear or via KVO or some other notification mechanism).
Have some private method that does my UI setup based on the property:
- (void)_updateUIForProperty {
// Handle UI update
}
Implement a setter for my public property that calls the _updateUIForProperty method if the view has been loaded already:
- (void)setProperty:(<#property type#>)property {
_property = property;
if(self.isViewLoaded) {
[self _updateUIForProperty];
}
}
And then to handle the case where the property was set prior to the view loading, we do something like this in viewDidLoad:
- (void)viewDidLoad {
// Other initialization
if(_property != nil) {
[self _updateUIForProperty];
}
}

How to initialize a custom view(controller) so it works both programmatically and in Interface Builder?

Suppose you implement a custom table view and a custom view controller (which mostly mimics UITableViewControllers behaviour, but when initialized programmatically, ...
#interface Foo : MyCustomTableViewController ...
Foo *foo = [[Foo alloc] init];
... foo.view is kind of class MyCustomTableView instead of UITableView:
// MyCustomTableView.h
#protocol MyTableViewDelegate <NSObject, UITableViewDelegate>
// ...
#end
#protocol MyTableViewDataSource <NSObject, UITableViewDataSource>
// ...
#end
#interface MyCustomTableView : UITableView
// ...
#end
// MyCustomTableViewController.h
#interface MyCustomTableViewController : UIViewController
// ...
#end
How should you implement/override init methods in correct order/ways so that you could create and use an instance of MyCustomTableView both by subclassing MyCustomTableViewController programmatically or from any custom nib file by setting custom class type to MyCustomTableView in Interface Builder?
It important to note that this is exactly how UITableView (mostly UIKit for that matter) works right now: a developer could create and use either programmatically or by creating from nib, whether be it File owner's main view or some subview in a more complex hierarchy, just assign data source or delegate and you're good to go...
So far I managed to get this working if you subclass MyCustomTableViewController, where I will create an instance of MyCustomTableView and assign it to self.view in loadView method; but couldn't figure out how initWithNibName:bundle:, initWithCoder:, awakeFromNib, awakeAfterUsingCoder:, or whatever else operates. I am lost in life cycle chain and end up with a black view/screen each time.
Thanks.
It is a real mystery how the UITableViewController loads its table regardless of if one is hooked up in interface builder, however I have came up with a pretty good way to simulate that behavior.
I wanted to achieve this with a reusable view controller that contains a MKMapView, and I figured out a trick to make it happen by checking the background color of the view.
The reason this was hard is because any call to self.view caused the storyboard one to load or load a default UIView if didnt exist. There was no way to figure out if inbetween those 2 steps if the user really didn't set a view. So the trick is the one that comes from a storyboard has a color, the default one is nil color.
So now I have a mapViewController that can be used in code or in storyboard and doesn't even care if a map was set or not. Pretty cool.
- (void)viewDidLoad
{
[super viewDidLoad];
//magic to work without a view set in the storboard or in code.
//check if a view has been set in the storyboard, like what UITableViewController does.
//check if don't have a map view
if(![self.view isKindOfClass:[MKMapView class]]){
//check if the default view was loaded. Default view always has no background color.
if([self.view isKindOfClass:[UIView class]] && !self.view.backgroundColor){
//switch it for a map view
self.view = [[MKMapView alloc] initWithFrame:CGRectZero];
self.mapView.delegate = self;
}else{
[NSException raise:#"MapViewController didn't find a map view" format:#"Found a %#", self.view.class];
}
}
The strategy I've used when writing such classes has been to postpone my custom initialization code as late as possible. If I can wait for viewDidLoad or viewWillAppear to do any setup, and not write any custom code in init, initWithNibName:bundle: or similar methods I'll know that my object is initialized just like the parent class no mater what way it was instantiated. Frequently I manage to write my classes without any overrides of these init methods.
If I find that I need to put my initialization code in the init methods my strategy is to write just one version of my initialization code, put that in a separate method, and then override all the init methods. The overridden methods call the superclass version of themselves, check for success, then call my internal initialization method.
If these strategies fail, such that it really makes a difference what way an object of this class is instantiated, I'll write custom methods for each of the various init methods.
This is how I solved my own issue:
- (void)loadView
{
if (self.nibName) {
// although docs states "Your custom implementation of this method should not call super.", I am doing it instead of loading from nib manually, because I am too lazy ;-)
[super loadView];
}
else {
self.view = // ... whatever UIView you'd like to create
}
}

Resources