I want to change the value of a label in my default view controller from a different class.
So I start a simple 'Single View Application' iOS project (Xcode5)
This automatically generates a ViewController for me (which I
understand is the root view controller)
I now add a label in my View and connect it to the ViewController (via IBOutlet mechanism)
I call this outlet 'gameStateLabel', so it looks like this in the ViewController.h file
#property (weak, nonatomic) IBOutlet UILabel *gameStateLabel;
Next, I have a completely separate class which has the logic for my code, and based on a condition in the logic I want to change the UIlabel.
So I try to do this from my other class:
Get an instance of the root view controller like this:
UIViewController * uvc = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
I think I now have an instance of the rootviewcontroller in uvc and should be able to reach in and change gameStateLabel.
BUT: I CANNOT do this uvc.gameStateLabel simply does not show up as a property even though it is clearly declared as a property and I've added the #synthesize for it also.
Any help will be greatly appreciated - I've been going nuts over this.
(For ref. I'm used to doing something similar on the Mac side where I'd declare a label as a property of the AppDelegate, get the instance of the Appdelegate and simply refer to the label property and change its text]
Here's the ViewController. Note that gameStateLable is a property
#import "ViewController.h"
#interface ViewController ()
#property (weak, nonatomic) IBOutlet UILabel *gameStateLabel;
#end
#implementation ViewController
#synthesize gameStateLabel;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
And here is my class cls1 (which inherits from NSObject)
#import "cls1.h"
#import "ViewController.h"
#implementation cls1
-(void) dummy{
UIViewController * uvc = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
// uvc does NOT show gameStateLabel in intellisense, i.e. uvc.gameStateLabel does NOT work
}
Add #import "mainRootVC.h" in you CustomClass.m file
And create object of mainRootVC such like,
mainRootVC *obj = [[mainRootVC alloc] init];
// Now you can access your label by
obj.gameStateLabel...
Do like this...
YourViewController *rootController =[(YourViewController*)[(YourAppDelegate*)
[[UIApplication sharedApplication]delegate] window] rootViewController];
Try the following too:
ViewController *controller = (ViewController*)self.window.rootViewController;
It will return the initial view controller of the main storyboard.
For sending information from a viewController to other viewController you have:
Delegates:Definiton and examples
NSNotificationCenter: Apple Documentation
NSString *label = #"label text";
[[NSNotificationCenter defaultCenter] postNotificationName:NAVIGATETO object:label userInfo:nil];
You can found tons of examples about those two. I recommend you use one of those.
Hope this helps a bit.
OK. I found two issues.
I had copied the code over from my Mac project and modified it. Something seems to have gone wrong.
I retyped the entire function and it solved most of the problem
uvc should be of the type ViewController* - not UIViewController* as I was doing
Thanks everyone for your replies - much appreciated.
Related
I am working on an iOS project and wanted to include a UIView that is reused on multiple screens in the application (appearing at the bottom of different UIViews). I am using a storyboard for the UI work so far but created an .xib file to be used by the reusable view (playerView in code below).
The view gets added but the button I have added to the View is unresponsive, also my background color cannot be changed on the view. I have tried to set background color programmatically and in the .xib with no luck. Very weird symptoms and I tried to instantiate the view from #5 of this article and probably did something wrong. I dont fully understand everything in the article which makes me nervous - for instance my showSubclassedView method returns IBAction but I just call the function name in code and dont use a button (I did hook up the buttons in the view as the article described though).
Here is the code:
EventViewController.m (where I trry and add PlayerView)
#import "EventViewController.h"
#import "PlayerView.h"
#interface EventViewController ()
-(IBAction)showSubclassedView;
#end
#implementation EventViewController
-(IBAction)showSubclassedView
{
[PlayerView presentInViewController:self];
}
PlayerView.h (.h for reusable view)
#import <UIKit/UIKit.h>
#import "Utils.h"
#class PlayerView;
#protocol PlayerViewDelegate
-(void)playerViewTouchedUp:(PlayerView*) playerView;
-(void)playerViewDidDismiss:(PlayerView*) playerView;
#end
#interface PlayerView : UIView
+(void)presentInViewController:(UIViewController<PlayerViewDelegate>*) playerView;
-(IBAction)viewTouchedUp;
-(IBAction)dismiss;
#end
PlayerView.m (.m for reusable view)
#import "PlayerView.h"
#interface PlayerViewOwner : NSObject
#property(nonatomic,weak) IBOutlet PlayerView *playerView;
#end
#implementation PlayerViewOwner
#end
#interface PlayerView ()
#property (nonatomic, weak) UIViewController <PlayerViewDelegate> *delegateViewController;
#end
#implementation PlayerView
+(void)presentInViewController:(UIViewController<PlayerViewDelegate> *)viewController
{
PlayerViewOwner *owner = [PlayerViewOwner new];
[[NSBundle mainBundle] loadNibNamed:NSStringFromClass(self) owner:owner options:nil];
owner.playerView.delegateViewController = viewController;
[viewController.view addSubview:owner.playerView];
}
-(IBAction)viewTouchedUp
{
//forward to delegate
NSLog(#"you clicked a button");
[self.delegateViewController playerViewTouchedUp:self];
}
-(IBAction)dismiss
{
[self removeFromSuperview];
// Forward to delegate
[self.delegateViewController playerViewDidDismiss:self];
}
#end
PlayerView.xib has a UIbutton on it that connects it to viewTouchedUp method in PLayerView.m
Is there anything I did wrong in the code above? Is this the best way to do a reusable view to display on other views?
Thank you!
Here's a workaround I found after not being able to solve this.
New implementation is from this article
I reverted my changes from my initial question and implemented this. It lacks the nice protocol separation for talking back and forth and I might need something like this later but I think now I can still implement a protocol to solve this.
At least with this solution I have usable items in my view and a background that changes!
I'm new in Objective-C Programming. I want to get the text from a UILabel and at least NSLog that when the application enters the background. but all i got was a null value.(I don't know why!). I'm using story boards to create the view and UILabel and i have used CTRL+Drag to hook the label to my code and synthesized that in the implementation file. but I just get a null value in AppDelegate.m codes.
These are my codes:
ViewController.h:
#property (strong, nonatomic) IBOutlet UILabel *Mylabel;
ViewController.m:
#synthesize Mylabel;
AppDelegate.m:
#import "ViewController.h"
- (void)applicationDidEnterBackground:(UIApplication *)application
{
ViewController * viewController = [[ViewController alloc] initWithNibName:#"ViewController" bundle:nil];
NSLog(#"%#",[[viewController Mylabel] text]);
}
ViewController * viewController = (ViewController *)self.window.rootViewController;
NSLog(#"This is my text: %#", viewController.Mylabel.text);
As you're new to Objective-C, I will point out that you should not ever every capitalize your property values (AKA Mylabel). Always use camel case for properties and methods. Methods use [ ] to call, properties you use . notation to access and set. You also don't need to synthesize Mylabel as this is done automatically for you. I recommend looking at the iOS by Apprentice book provided by raywenderlich.com to get up to speed.
You are creating a new instance of ViewController in the applicationDidEnterBackground: method of AppDelegate.m, rather than accessing the existing instance. As a quick fix, you should find self.window.rootViewController points to your existing instance. So change your code to:
NSLog(#"Label text is %#",self.window.rootViewController.Mylabel.text);
Note the use of 'dot' notation to access properties (it's easier). And FYI, tradition has it that properties begin with a lowercase letter; you might want to change Mylabel to myLabel, to get into the habit!
Just started xcode 5 and xctest. How do I test that a view loads on button press. I have programatically added method that gets called when the rightBarButtonItem is clicked
action:#selector(onSettingsButton)
and in onSettingsButton
-(void) onSettingsButton{
SettingsViewController *svc = [[SettingsViewController alloc] init];
[self.navigationController pushViewController:svc animated:YES];
}
How to write xctest to ensure SettingsViewController brings up the Settings view? Thank you.
You need an interaction test — that is, a test that checks interactions between objects. In this case, you want to test that -pushViewController:animated: is called on the navigation controller with a SettingsViewController. So we want to put a mock object into self.navigationController which we can ask, "Were you called as expected?"
I'll assume a simple name for the class: MyView.
The way I'd do this by hand is to Subclass and Override navigationController. So in my test code, I'd do something like this:
#interface TestableMyView : MyView
#property (nonatomic, strong) id mockNavigationController;
#end
#implementation TestableMyView
- (UINavigationController *)navigationController
{
return mockNavigationController;
}
#end
Now instead of creating a MyView, the test will create a TestableMyView and set its mockNavigationController property.
This mock can be anything, as long as it responds to -pushViewController:animated: and records the arguments. Here's a simple example, by hand:
#interface MockNavigationController : NSObject
#property (nonatomic) int pushViewControllerCount;
#property (nonatomic, strong) UIViewController *pushedViewController;
#property (nonatomic) BOOL wasPushViewControllerAnimated;
#end
#implementation MockNavigationController
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
self.pushViewControllerCount += 1;
self.pushedViewController = viewController;
self.wasPushViewControllerAnimated = animated;
}
#end
Finally, here's a test:
- (void)testOnSettingsButton_ShouldPushSettingsViewController
{
// given
MockNavigationController *mockNav = [[MockNavigationController alloc] init];
TestableMyView *sut = [[TestableMyView alloc] init];
sut.mockNavigationController = mockNav;
// when
[sut onSettingsButton];
// then
XCTAssertEquals(1, mockNav.pushViewControllerCount);
XCTAssertTrue([mockNav.pushedViewController isKindOfClass:[SettingsViewController class]]);
}
These things can be simplified by using mock object frameworks such as OCMock, OCMockito, or Kiwi's mocking. But I think it helps to start by hand first, so that you understand the concepts. Then choose the tools that help. And if you know how to do it by hand, you'll never say, "Mocking framework X doesn't do what I need! I'm stuck!"
Found one way. Maybe there are others ..
- (void)testSettingsViewShowsWhenSettingsButtonIsClicked{
[self.tipViewController onSettingsButton];
id temp = self.tipViewController.navigationController.visibleViewController;
XCTAssertEqual([temp class], [SettingsViewController class], #"Current controller should be Settings view controller");
}
First call the onSettingsButton, which is the same as clicking the button, but not really. Maybe it's okay for this simple test case? How to simulate the actual press?
Then get the current view from the tipviewcontoller which is the rootview of the app and check that it is a SettingsViewController.
ok, this may be a very basic question and I apologise if it is, but its driving me mad and I really need to get a better understanding.
I have an app that I am developing and using a new view controller and xib file for each page.
I call each page by using this code:-
HelpVC = [[[HelpViewController alloc] initWithNibName:#"HelpViewController" bundle: [NSBundle mainBundle]]autorelease];
[self presentModalViewController:HelpVC animated:YES];
Which works great.
However if I have this declared in a file and I then want to call it somewhere else I am getting errors all over the place.
is there any way to initialise all the xib files first in a class file and then call them from that file when needed??
Thanks
This is the error I get back
error: expected specifier-qualifier-list before 'UserInputViewController'
It seems to cause all of my declarations in the .h file to error with the message above.
Some sample code is:-
.h file
#import <UIKit/UIKit.h>
#import "CgePWViewController.h"
#import "LogBackInViewController.h"
#import "LoginViewController.h"
#interface SettingsViewController : UIViewController
{
CgePWViewController *cPWVC;
LogBackInViewController *LBIVC;
LoginViewController *login;
}
#property(nonatomic, retain) CgePWViewController *cPWVC;
#property(nonatomic, retain) LogBackInViewController *LBIVC;
#property(nonatomic, retain) LoginViewController *login;
so basically as soon as I have added in LoginViewController it causes everything else to error, if I take this out then the app runs perfectly.
Does your LoginViewController.h imports the SettingsViewController as well? It seems to be a "cross reference" (I don't know if this term makes sense in English as it does in Portuguese).
Before your
#interface SettingsViewController : UIViewController
put a
#class LoginViewController;
and check if it helps.
Don't initialise your view controllers to put them in properties. Instead, create them as soon as you need them.
Let's assume you want to show a view controller when somebody pushes a button. Add this method to your view controller (let's assume it's named MyFirstViewController):
- (IBAction)someButtonDidPush: (UIButton *)sender {
MySecondViewController *vc = [[MySecondViewController alloc] init];
// Do stuff and then show it. One way may be like this:
[self presentViewController: vc animated: YES completion: nil];
}
Just hook up the button with the method above. No properties needed! You're (probably) done.
The Problem
An IBOutlet is released before I have a chance to use it.
What I Want
I want to access a navigation controller from my app delegate so I can reload a table view.
My Setup
I have:
A Main.xib that's set as my main interface in target settings
An IBOutlet to the navigation controller as an ivar on my app delegate
This IBOutlet hooked up to the correct navigation controller in Main.xib
App Delegate is instantiated in the xib but not set as File's Owner
I'm using ARC, Xcode 4.3.2 and iOS5.1
What I've Tried
Changing deployment target
Putting a break point on dealloc for the navigation controller, app delegate - they're never called
Reading everything I can find on ARC and IBOutlets - nothing seems to contradict what I'm doing
Creating a fresh project with just a the minimum classes required - I see exactly the same problem
Code
KPAppDelegate.h
#interface KPAppDelegate : UIResponder <UIApplicationDelegate> {
IBOutlet KPBrowseExpensesNavigationController *nc;
}
#property (strong) IBOutlet KPBrowseExpensesNavigationController *nc;
KPAppDelegate.m
#implementation KPAppDelegate
#synthesize nc;
-(void)setNc:(KPBrowseExpensesNavigationController *)nc_ {
nc = nc_; // This gets called on view load and nc gets set.
}
...snip...
// This is called about 5 seconds after app startup
-(void)objectLoader:(RKObjectLoader *)objectLoader didLoadObjects:(NSArray *)objects {
// By the time we get here, nc is nil.
UITableViewController *tvc = [[nc viewControllers] objectAtIndex:0];
[[tvc tableView] reloadData];
}
#end
UPDATE
I must be doing something really silly here. Even an incredibly simple project still shows this problem. See link below.
Download a simple test project that shows the problem.
In Window nib, set the FilesOwner Class as UIApplication and then point it's delegate from Outlets to the AppDelegate object. This is what is wrong in your project example.
is your outlet from the Interface Builder set as an KPBrowseExpensesNavigationController type?
If not it is not going to create the connection between your nib and ViewController.
You should set its Custom Class as KPBrowseExpensesNavigationController in the Identity Inspector
I am not sure why you declare it as a property & a non-property. I should do something like this:
#interface KPAppDelegate : UIResponder <UIApplicationDelegate>
#property (nonatomic, strong) IBOutlet KPBrowseExpensesNavigationController *nc;
And in your implementation:
#implementation KPAppDelegate
#synthesize nc = _nc; // So you don't accidentally use nc
...snip...
// This is called about 5 seconds after app startup
-(void)objectLoader:(RKObjectLoader *)objectLoader didLoadObjects:(NSArray *)objects {
// By the time we get here, nc is nil.
UITableViewController *tvc = [[**self.nc** viewControllers] objectAtIndex:0];
[[tvc tableView] reloadData];
}
#end
Hope this helps!
I didn't see where you alloc your nav controller. Just declaring the property won't assign any value to it, so it would be nil. In you -didFinishLaunchingWithOptions in the app delegate, set your alloc/init statement. Everything else looks fine.
KPBrowseExpensesNavigationController *nc = [[KPBrowseExpensesNavigationController alloc] init];
If you have a custom init, you can use that too, but just make sure to set it up before you try and use it.