Method in UIViewController being called from AppDelegate is not using existing objects - ios

I've been driving myself nuts trying to figures this out, and have tried just about everything to my knowledge. I'm hoping someone can figure out this issue, let me explain my current setup.
I'm using the Bump-api within my iOS app. I have the configureBump method in the AppDelegate which is called in the initial launch of the app. So the app is connected to the Bump servers from the start.
Next, I have a NavigationController with the following UIViewControllers stacked in this order:
ViewControllerA -> ViewControllerB -> ViewControllerC
VCA (ViewControllerA) is the first view when starting the app. In the ViewWillLoad I have the following code:
myAppDelegate = [[UIApplication sharedApplication] delegate];
[myAppDelegate vibrateBump:NO];
vibrateBump is a method in my AppDelegate which I use to turn off & on bump. In which case, I want it off on VCA since there is nothing I want to be transfered.
However, in VCB I have the same strip of code with vibrateBump:YES, this is where I want Bump to work. This all works fine, dont worry I'm getting there.
Now, in the configureBump method within the AppDelegate, I have it setup to call a method from VCB when bumped. This is when things get weird. Here is an example of the method being called in VCB:
#implementation VCB {
FMDatabase * db;
}
- (void)viewDidLoad{
//db is set up here...
}
-(void) otherMethodWithinVC{
//Some action here...
}
-(void) myMethod:(NSArray*)myArray{
[db open] //This does now work
Do some stuff here.... //All this works
[db close] //This does now work
[self otherMethodWithinVC] //This does now work
}
Why is [db open] not working? its as if there is another instance of myMethod being called. When I changed it up while testing, and decided to create db within myMethod, db started to work.
Here is how I have it setup within the configureBump method:
- (void) configureBump {
VCB * vcb = [[VCB alloc] init];
[[BumpClient sharedClient] setChannelConfirmedBlock:^(BumpChannelID channel) {
//Some action here...
}];
[[BumpClient sharedClient] setDataReceivedBlock:^(BumpChannelID channel, NSData *data) {
dataArray = [NSKeyedUnarchiver unarchiveObjectWithData:data];
if([dataArray count]>0){
if([vcb myMethod:dataArray];
}
}];
//Some action here...
}
Any ideas?

When you are calling mymethod from the appdelegate you are creating a different instance of VCB controller but in the case of
myAppDelegate = [[UIApplication sharedApplication] delegate];
you are creating a single instance of the appdelegate like a singleton

Related

iOS - singleton is not working as supposed in delegate

Currently I'm working on an app that uses four protocols for communication between classes. Three are working fine, but one is still not working. I've set it up same as the others but the delegate is always losing its ID. I'm quite new to Objective-C so I can't get to the bottom of it. Here is what I did:
I have a MainViewController.h with the delegate
#property (weak, nonatomic) id <PlayerProtocol> player;
and a MainViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
[[Interface sharedInstance] Init];
NSLog(#"Player ID: %#", _player);
NSLog(#"viewDidLoad: %#", self);
}
- (void)sedPlayer:(id) pointer{ //sed is no typo!
_player = pointer;
NSLog(#"sedPlayer ID: %#", _player);
NSLog(#"sedPlayer: %#", self);
}
+ (instancetype)sharedInstance {
static dispatch_once_t once;
static id sharedInstance;
dispatch_once(&once, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
In the Interface.m (NSObject)
- (void)Init {
[[MainViewController sharedInstance] sedPlayer:self];
}
And of course a protocol.h but this is not of interest as the delegate does the trouble! When I run the code I get the following output on the console:
sedPlayer ID: <Interface: 0x1700ab2e0>
sedPlayer: <MainViewController: 0x100406e30>
Player ID: (null)
viewDidLoad: <MainViewController: 0x100409550>
So it is obvious that the singleton is not working as the instance of the MainViewcontroller is different. For the singleton I'm using the dispatch_once standard method as I do with the other protocols that work fine. ARC is turned on. Does anyone has a clue what is wrong here and why the singleton is not working?
Here's how I think you ended up with two instances of the MainViewController. The first one, I assume, is created when navigating to the screen associated with MainViewController. The second one is created when you call [MainViewController sharedInstance] in Interface.m.
As the ViewController view is lazy loaded ("View controllers load their views lazily. Accessing the view property for the first time loads or creates the view controller’s views." from the Apple docs under ViewManagement), you see the viewDidLoad: <MainViewController: 0x100409550> log only once, when the first MainViewController gets navigated to and loads up the view.
Here's my suggestion:
Since you do the Interface initializing in the - (void)viewDidLoad, you might as well set self.player = [Interface sharedInstance].
The code would look something like this:
- (void)viewDidLoad {
[super viewDidLoad];
self.player = [Interface sharedInstance];
NSLog(#"Player ID: %#", _player);
NSLog(#"viewDidLoad: %#", self);
}
You should also get rid of - (void)sedPlayer:(id) pointer and + (instancetype)sharedInstance in your MainViewController. It is never a good idea to have a ViewController singleton, since you might end up messing up the navigation or having multiple states of it.
For a more in-depth article on avoiding singletons, you can check objc.io Avoiding Singleton Abuse

Highlighting a button from another class in objective-C, iOS

I have an app that includes a bluetooth LE class for handling a BT connection. When a characteristic value is changed I want code in the BT class to change a button state on the main viewcontroller. I'm new to obj-c and usually use stackoverflow to help with my understanding. I have the following defined in my viewcontroller:
#interface myViewController : UIViewController<CLLocationManagerDelegate, UITableViewDataSource, UITableViewDelegate, UINavigationBarDelegate, UIActionSheetDelegate>
#property (weak, nonatomic) IBOutlet UIButton *eraseButton;
within the view controller, I can happily change the button state:
[self.eraseButton setHidden:YES];
and
-(void) deselectEraseButton
{
[self.eraseButton setSelected:NO];
}
within the BT class I have tried many things (changing a property of viewcontroller which changes the button state via a timer, calling a method that directly changes the button state etc) and although the code is executed (I have breakpoints set on the code in myViewController), the button state isn't changed. For example:
myAppDelegate *app = (myAppDelegate *)[[UIApplication sharedApplication] delegate];
app.vCtrl=[[myViewController alloc] init];
[app.vCtrl deselectEraseButton];
What am I doing wrong and what's the best way of doing this? I'm sure it shouldn't be this hard!
UPDATE:
with the help of Zhi-Wei Cai (thanks), I'm made some changes but alas, it's still not working:
I added the following to myAppDelegate.h:
#property (strong, nonatomic) myViewController* vCtrl;
I added the following to myAppDelegate.m:
-(void)eraseButton:(BOOL)state{
[vCtrl accessEraseButton:state];
}
From the BT class, I do the following:
[self performSelectorOnMainThread:#selector(test) withObject:nil waitUntilDone:YES];
with the following method in that class:
-(void) test
{
myAppDelegate *app = (myAppDelegate *)[[UIApplication sharedApplication] delegate];
[app eraseButton:0];
}
This called myViewController and on thread1 did the following:
-(void) accessEraseButton: (BOOL) state
{
if (state==0)
{
[self.eraseButton setSelected:NO];
[self.view setNeedsDisplay];
}
else
{
[self.eraseButton setSelected:YES];
}
}
I then added [self.view setNeedsDisplay]; for good luck. The button is still not changing state but the code is running (I can hit a breakpoint and it breaks on the method and the method is running on thread1)?!? Any ideas?
* UPDATE 2 *
Ok, I've worked out what I was doing wrong. I was doing all of the above without properly referencing the viewcontroller. So although the code was executing, the instance of the viewcontroller wasn't correct and all the button handles were nil. I needed to add the following to myViewController.m, in viewDidLoad:
myAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
appDelegate.myViewController = self;
Now it works. Happy days...
Because when you do app.vCtrl = [[myViewController alloc] init];, you actually created a new instance of the UIViewController, not the original one that's on your screen.
I didn't test them, but you can achieve what you wanted by:
Via Interface Builder: Setup an IBOutlet of the UIViewController in your myAppDelegate, connect it to the ViewController within Interface Builder, then create a method in myAppDelegate to change it or access it directly using myAppDelegate as it's a property. You can now call it within your BT class e.g. [app deselectEraseButton] or [app.myViewController deselectEraseButton] or even do something with the button if the button is a property of the AppDelegate/VC.
Via Delegate: Create a #protocol to setup a delegate for the BT class, so the it can do callback within other classes such as myAppDelegate.
Via NSNotificationCenter: Use - (void)addObserver:(id)notificationObserver selector:(SEL)notificationSelector name:(NSString *)notificationName object:(id)notificationSender and - (void)postNotificationName:(NSString *)notificationName object:(id)notificationSender userInfo:(NSDictionary *)userInfo to do it. This will work app-wide.
I believe the above three already has tons of code example on SO, just search them and you will get what you need.
I've updated by original question with 2 updates that lead to a final understanding and solution. Hope it helps others who may be similarly confused!

how To set and pass value of variable which also accesible in another View objective C

In AppDelegate.h
#property(strong,nonatomic)NSString *str2;
In ViewController.m
AppDelegate *c3=[[AppDelegate alloc]init];
c3.str2= #"Hello";
NSLog(#"Output:-%#",c3.str2);
Output:-Hello
Navigation code in Tableview didselect method(view Controller):-
Class2 *c2=[self.storyboard instantiateViewControllerWithIdentifier:#"cl2"];
[self.navigationController pushViewController:c2 animated:YES];
In Class2.m:-
AppDelegate *c3=[[AppDelegate alloc]init];
NSLog(#"%#",c3.str2);
Output:-Null
First, let's fix your current approach: the reason some recommend using app delegate to store shared values is that there is one easily accessible instance of it. You never create a new app delegate, but access the shared one instead:
AppDelegate *c3 = (AppDelegate*)[[UIApplication sharedApplication] delegate];
Once you replace [[AppDelegate alloc]init] with the above, your code will start working the way you expect.
However, this solution is not ideal. App delegate is not supposed to be a place for storing shared values; they should be stored in your model object (as "M" in MVC, Model-View-Controller). Create a singleton model object, place shared variable in it, and use that shared variable by accessing the singleton from different view controllers.
In AppDelegate.h
#property(strong,nonatomic) NSString *str2;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
_str2 = "Hello"
}
In ViewController.m and any other view controller that wants to access str2:
AppDelegate *delegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
NSLog(#"Output: %#", delegate.str2);
Never create AppDelegate object on your own. Instead, access its instance via sharedApplication.

iOS singleton viewDidLoad empty and on viewDidAppear not

I created a singleton in ios7 like this:
SharedData.h
#interface SharedData : NSObject
{
}
+ (id)sharedInstance;
#property (strong, nonatomic) NSMutableArray *list;
#end
SharedData.m
#import "SharedData.h"
#implementation SharedData
#synthesize list;
// Get the shared instance thread safe
+ (SharedData *)sharedInstance {
static dispatch_once_t once = 0;
static SharedData *sharedInstance = nil;
dispatch_once(&once, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (id)init {
self = [super init];
if (self) {
//initialize
list = [[NSMutableArray alloc] init];
}
return self;
}
#end
I always use this code to access this class:
SharedData *sharedData = [SharedData sharedInstance];
The problem is now when I switch the view in my viewDidLoad method the list is empty but in my viewDidAppear method everything is fine. Any ideas?
EDIT:
This is the code how I change the views:
SharedData *sharedData = [SharedData sharedInstance];
//clear feed and add new feed
[sharedData.list removeAllObjects];
[sharedData.list addObjectsFromArray:newList];
//show new gui
[self.navigationController performSegueWithIdentifier:#"goToMain" sender:self];
NOTE: I push from a normal ViewController to a TabBarController -> NavigationController -> TableViewController to display the list.
I guess you have the confusion between these two viewcontroller methods:
-(void)viewDidLoad{
//
}
&
-(void) viewDidAppear{
//
}
viewDidAppear is the method which is called each time your view changes but viewDidLoad is the method which is not necessarily called each time your view changes.
ViewDidLoad method is called when view loads for the first time, after that it doesn't get called until the views are removed/released.
P.S: I suggest you to put the breakpoint in your viewDidLoad and viewDidAppear method and feel it. Your answer lies there.
Hope this helps you alot.
Good Luck.
The problem was i created a segue which went from the button to the next view. Because of this the viewDidLoad gets earlier called than the list assigned. I just changed the segue to go from view to view.
How are you changing from one viewController to the other? Wich classes are the parents of your destination ViewController?,
If you are modifying properties of the view in the prepareForSegue method... you are forcing the view to load.
For example, you are setting the list of your singleton in prepareForSegue, but before setting the list you are modifying a property of your destination viewController. (doing something like destVC.view = XXX or destVC.viewControllers = XX if you are subclassing a UITabBarViewController...) Then you are triggering the viewDidLoad method , and it's executing before you have set the list to the correct value.
Or maybe you are seguing in two different places to the destinationViewController. And when the viewDidLoad happens, you still have not updated the list on the singleton.
Here is the transcription of the chat with the poster of the question: https://chat.stackoverflow.com/transcript/55218

Pass BOOL value

I am working on an app and I got stuck at the point where I can't seem to retrieve the value of a BOOL set in a class.
I spent too much time already on it, been through all the questions I found that seem to cover the matter.
The bad thing here is that I get something, but not what I need (I get a 0, which means, I guess, that the value wasn't retrieved correctly as it should be 1).
The things I tried are :
pass a pointer to my first class and access to my BOOL like this:
//in some method
self.pointerFirstClass.myBOOL;
NSLog(#"%d", firstClass.myBOOL); => This gives 0!
by declaring it (talking of the pointer) as a property in my second class (and importing the h. file from my first class, where my BOOL is declared as property too):
#property FirstClass *pointerFirstClass;
But I got 0 using this.
The other shot I gave was add my BOOL in the first class and create an instance of the class in my second class
//in some method
FirstClass *firstClass = [[FirstClass alloc] init];
if (firstClass.myBOOL){
//Do something
}
NSLog(#"%d", firstClass.myBOOL); => This gives 0!
But I got 0 too.
As Booleans are primitive types, like in C, I get a bit confused since I am new to object-oriented programming, I don't know how I could like create a getter for this, for example.
I also tried to do a - (BOOL)getBOOLValue method in my first class, and call this method in my second class and assign it to a BOOL in that second class.
But the result wasn't better.
Am I missing something?
Is there a way to get my value that I didn't think of or didn't know about yet?
I am running low on thoughts on how to get around this, it shouldn't be that hard IMO so I hope it is something simple that I just left aside.
EDIT :
Some actual code. I am working between 2 files called AppDelegate (yes, the actual one) and WelcomeViewController (so a VC).
AppDelegate.h
#interface AppDelegate : UIResponder <UIApplicationDelegate>
{
BOOL inRegion; //thought of this making my BOOL as a property of AppDelegate
}
#property (strong, nonatomic) UIWindow *window;
#property BOOL inRegion; //Declaring my BOOL here to make it accessible for another class
- (BOOL)getBOOLValue; //An attempt to pass my BOOL value
AppDelegate.m
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
{
if (state == CLRegionStateInside)
{
self.inRegion = YES; //Set my BOOL to TRUE
}
else if (state == CLRegionStateOutside)
{
self.inRegion = NO; //Else set it to False
}
- (BOOL)getBOOLValue
{
return inRegion; //Tried to create a custome "getter"
}
WelcomeViewControler.m (I changed nothing in the .h file)
I said I tried many things, right now, this is the last version of my code.
//Simply trying to do a Segue on a condition...
- (IBAction)onClick:(id)sender {
AppDelegate *appDelegate = [[AppDelegate alloc] init];
if (appDelegate.inRegion) {
[self performSegueWithIdentifier:#"WelcomeToDetection" sender:self];
}
else
{
//Do Nothing
}
}
As said, I want to retrieve the BOOL value of the AppDelegate.
Thank you.
This code doesn't make sense:
self.pointerFirstClass.myBOOL;
NSLog(#"%d", firstClass.myBOOL); => This gives 0!
The first line doesn't do anything. You're not assigning anything to the property, and you're not doing anything with the value. Furthermore, the second line doesn't relate to the first line in any way that we can see from the code you've provided. Try this instead:
self.pointerFirstClass = [[FirstClass alloc] init];
self.pointerFirstClass.myBOOL = YES;
NSLog(#"myBOOL = %d", self.pointerFirstClass.myBOOL);
In other words, you need to be sure that self.pointerFirstClass points to a valid object. And then you need to make sure that you've assigned the value you want to the myBOOL property of that object.
Update: This looks like a case where you're talking to the wrong object. Look at this:
- (IBAction)onClick:(id)sender {
AppDelegate *appDelegate = [[AppDelegate alloc] init];
This is surely not what you really want. The application object is a single object -- a real singleton, in fact, meaning that there is and can be only one application object. That object has a delegate object, and that's a specific instance of your AppDelegate class. In this code, though, you're creating a new instance of AppDelegate, one that's different from the one that the application is using. Any changes that are made to the actual application delegate in response to messages from the application will not be reflected in the new object that you've created.
What I think you want is to get the actual application delegate object, and you can do that using:
[[UIApplication sharedApplication] delegate];
So, change your code to look like this:
- (IBAction)onClick:(id)sender {
AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];;
if (appDelegate.inRegion) {
[self performSegueWithIdentifier:#"WelcomeToDetection" sender:self];
}
// note: you don't need an else clause if it doesn't do anything
}
That way, you'll be talking to the same object that the app uses, which is the one that has the inRegion property set in response to the location manager call.
UPDATE - Now we can see your code the problem is obvious, you are trying to access the appDelegate by creating a new one...
- (IBAction)onClick:(id)sender {
AppDelegate *appDelegate = [[AppDelegate alloc] init];
Instead you should be doing this....
- (IBAction)onClick:(id)sender {
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate]
--
Not sure if you are posting your actual code? but the first example you give...
self.pointerFirstClass.myBOOL;
NSLog(#"%d", firstClass.myBOOL); => This gives 0!
Shouldn't the second line be
NSLog(#"%d", self.pointerFirstClass.myBOOL);
Also this property...
#property FirstClass *pointerFirstClass;
Won't retain it once you've set it, it needs to be
#property (nonatomic,strong) FirstClass *pointerFirstClass;
In the second example...
FirstClass *firstClass = [[FirstClass alloc] init];
if (firstClass.myBOOL){
//Do something
}
NSLog(#"%d", firstClass.myBOOL); => This gives 0!
You allocate and initialise a new FirstClass object and then check the property straight away, if you are not setting this to YES in the init then it will be false
Like I say, maybe you're not posting your actual code?
I guess what you want is initializing myBOOL to 1.
If so, you need do something as following
#implement FirstClass
- (id)init
{
self = [super init];
if(self) {
_myBOOL = 1;
}
return self;
}
// Other methods
#end
EDIT:
The comments is why you get 0.
- (IBAction)onClick:(id)sender {
AppDelegate *appDelegate = [[AppDelegate alloc] init]; // this is the problem.
// you create a new appdelegate,
// and never call locationManager:didDetermineState:forRegion:
if (appDelegate.inRegion) {
[self performSegueWithIdentifier:#"WelcomeToDetection" sender:self];
}
else
{
//Do Nothing
}
}
rewrite your code as following:
- (IBAction)onClick:(id)sender {
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
if (appDelegate.inRegion) {
[self performSegueWithIdentifier:#"WelcomeToDetection" sender:self];
}
else
{
//Do Nothing
}
}

Resources