NSString property gets null after init method in other methods? - ios

I got a little problem. Here's my source code:
The .h:
#import <UIKit/UIKit.h>
#interface TBNoteViewController : UIViewController <UITextViewDelegate>
#property (nonatomic, copy) NSString *noteKey;
- (IBAction)dismissKeyboard:(id)sender;
- (id)initWithNoteIndex:(int)index;
#end
The .m:
#import "TBNoteViewController.h"
#import "PSPDFTextView.h"
#include <tgmath.h>
#interface TBNoteViewController ()
{
CGRect _keyboardRect;
BOOL _keyboardVisible;
}
#property (nonatomic, strong) UIBarButtonItem *rightBarButton;
#property (nonatomic, strong) PSPDFTextView *textView;
#end
#implementation TBNoteViewController
#synthesize noteKey;
- (id)initWithNoteIndex:(int)index
{
self = [super init];
if (self) {
NSMutableArray *data = [[NSMutableArray alloc] initWithArray:[[NSUserDefaults standardUserDefaults] objectForKey:#"Notes"] copyItems:YES];
self.noteKey = [NSString stringWithFormat:#"%#", [data objectAtIndex:index]];
NSLog(#"1: %#", self.noteKey);
}
return self;
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
NSLog(#"2: %#", self.noteKey);
// Register notifications
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillShowNotification:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillHideNotification:) name:UIKeyboardWillHideNotification object:nil];
_rightBarButton = [self.navigationItem rightBarButtonItem];
self.navigationItem.rightBarButtonItem = nil;
PSPDFTextView *textView = [[PSPDFTextView alloc] initWithFrame:self.view.bounds];
textView.delegate = self;
textView.autoresizingMask = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth;
textView.font = [UIFont systemFontOfSize:17.f];
[self.view addSubview:textView];
self.textView = textView;
[self.textView setTextContainerInset:UIEdgeInsetsMake(5, 12, 5, 12)];
self.textView.alwaysBounceVertical = YES;
self.textView.keyboardAppearance = UIKeyboardAppearanceDark;
textView.text = self.noteKey;
[super viewDidLoad];
// Do any additional setup after loading the view.
}
So my problem is that the property noteKey has a string, but in viewDidLoad it is null.
So, you can see the NSLogs, the output would be (just copied that):
2014-02-13 23:07:21.197 TaskBeater[3205:70b] 1: Hoho.
2014-02-13 23:07:21.199 TaskBeater[3205:70b] 2: (null)
I would highly appreciate if you could help me, I have no idea what's wrong there.

It looks to me like you're instantiating two different "TBNoteViewController" instances.
You're doing one programatically (in code), which is how you're getting the "noteKey" property to appear properly.
Then the second one is created because you loaded it from a XIB file, which is why "viewDidLoad:" gets called.
After getting rid of the extra one that you're calling in code, create an outlet to the one you created via the XIB file, and then you can set properties on that and you should be all set.

Related

ios custom delegate is null after second click in navigation controller

I have a custom UIWebView (EpubWebView), with a custom NSURLCache (EpubCache) for handling requests.
i created a custom delegate for handling request.
EpubCache.h
#protocol EpubCacheDelegate <NSObject>
#required
- (NSCachedURLResponse *)hadleRequest:(NSURLRequest *)request;
#end
#interface EpubCache : NSURLCache
#property (nonatomic, weak) id <EpubCacheDelegate> cacheDelegate;
#end
EpubCache.m
import "EpubCache.h"
#interface EpubCache ()
#end
#implementation EpubCache
- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request
{
return [self.cacheDelegate hadleRequest:request];
}
#end
EpubWebView .h
#interface EpubWebView : UIWebView <UIWebViewDelegate, EpubCacheDelegate>
#property (strong, nonatomic) EpubCache *mLocalCache;
#end
EpubWebView.m
- (void) localInit
{
self.mLocalCache = [[EpubCache alloc] init];
self.mLocalCache.cacheDelegate = self;
[NSURLCache setSharedURLCache:self.mLocalCache];
}
- (NSCachedURLResponse *)hadleRequest:(NSURLRequest *)request
{
// return handled request
}
on the other hand i have a navigationcontroller with a tableview and the destination view controller have this webview.
when i ran the app and click on an item in tableview, everything is fine and delegate works as expected.
if i click back and click on other item in tableview, things goes wrong, the cachedResponseForRequest getting called but the hadleRequest wont, i checked and findout that the delegate is null!
i can not figure out what is happening here.
any help would be appreciated.
UPDATE 1
EpubWebView.m
- (id)init
{
self = [super init];
if (self)
{
[self localInit];
}
return self;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self)
{
[self localInit];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if(self)
{
[self localInit];
}
return self;
}
UPDATE 2
the segue of the tableview that bring up the view controller that contain EpubWebView
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSIndexPath *indexPath = [self.collectionView indexPathForCell:sender];
BookViewController *bookController = segue.destinationViewController;
bookController.mBook = booksList[indexPath.row];
}
and BookViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
mWebView = [[EpubWebView alloc] initWithFrame:self.mainView.frame];
[self.mainView addSubView:mWebView];
[mWebView setBook:self.mBook];
}
You need to make a few modifications. First, remove your "localInit" method, then create a new function in EPubWebView:
- (void) setCache: (EpubCache *)localCache
{
localCache.cacheDelegate = self;
}
Now, you can create and hold onto your cache in your BookViewController via these lines in the .m file:
#interface BookViewController ()
#property (strong, nonatomic) EpubCache *mLocalCache;
#end
and change your BookViewController's viewDidLoad method to look like:
- (void) viewDidLoad {
self.mLocalCache = [[EpubCache alloc] init];
// only need to do this once, at viewDidLoad time
[NSURLCache setSharedURLCache:self.mLocalCache ];
[super viewDidLoad];
mWebView = [[EpubWebView alloc] initWithFrame:self.mainView.frame];
[mWebView setCache:self.mLocalCache];
[self.mainView addSubView:mWebView];
[mWebView setBook:self.mBook];
}

iOS - Setting block to block directly does not work

The title says it, I'd like to ask why the following does not work, as it should imho.
// ViewController.m
#import "B.h"
...
#implementation ViewController
{
B *bInstance;
}
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])
{
bInstance = [[B alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
[bInstance setBlockCalled:^(NSDictionary *dict) {
NSLog(#"%#", dict[#"key"]);
}];
[self.view addSubview:bInstance];
}
return self;
}
// B.h
#import <UIKit/UIKit.h>
#interface B : UIView
#property (nonatomic, copy) void (^blockCalled)(NSDictionary *);
#end
// B.m
#import "B.h"
#import "A.h"
#implementation B
{
A *aInstance;
void (^blockCalled)(NSDictionary *);
}
#synthesize blockCalled;
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
aInstance = [[A alloc] initWithFrame:frame];
[aInstance setBlockCalled:blockCalled];
[self addSubview:aInstance];
}
return self;
}
#end
// A.h
#import <UIKit/UIKit.h>
#interface A : UIView
#property (nonatomic, copy) void (^blockCalled)(NSDictionary *);
#end
// A.m
#import "A.h"
#implementation A
{
void (^blockCalled)(NSDictionary *);
}
#synthesize blockCalled;
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button setFrame:CGRectMake(0, 0, 100, 100)];
[button setBackgroundColor:[UIColor redColor]];
[button addTarget:self action:#selector(buttonClicked) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:button];
}
return self;
}
- (void) buttonClicked
{
blockCalled(#{#"key":#"value"});
}
#end
What I want to do is 'traverse up the view hiearchy', and as far as I see it, I'm assigning a block variable with the same arguments, so I'd expect it to work. Any reason why this is a wrong idea?
EDIT: added more complete example as of when this issue might happen.
EDIT2: added MCVE, which I've tested.
After I've checked the MCVE, the code crashes on the line blockCalled(#{#"key":#"value"}); in A.m because the blockCalled is nil.
Updated question: I'd like to know why calling [aInstance setBlockCalled:blockCalled] doesn't set the blockCalled in A, as it seems to me to be the same as
[aInstance setBlockCalled:^(NSDictionary *dict)
{
__strong typeof (self) strongSelf = self;
strongSelf.blockCalled(dict);
}];
#implementation ViewController
{
B *bInstance;
}
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])
{
bInstance = [[B alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
[bInstance setBlockCalled:^(NSDictionary *dict) {
NSLog(#"%#", dict[#"key"]);
}];
[self.view addSubview:bInstance];
}
At the moment of initializing bInstance, you haven't set the block yet. It means
aInstance = [[A alloc] initWithFrame:frame];
[aInstance setBlockCalled:blockCalled];
is called before
[bInstance setBlockCalled:^(NSDictionary *dict) {
NSLog(#"%#", dict[#"key"]);
}];
You should override the block setter in B and call it on A.
// B.m
-(void)setBlockCalled(void(^)(NSDictionary*))passedBlock{
[a setBlockCalled:passedBlock];
}
The code in your question (a) uses ivars and #synthesize statements that are unnecessary; and (b) this code snippet is insufficient to reproduce the crash you describe.
Having said that, there are two possible source of crashes that are suggested by the code sample in the question, namely: (a) the code fails to remove the observer if A is deallocated; and (b) it really should double-check to make sure that blocks are non-nil before trying to call them.
But, consider the following:
// A.h
#interface A : UIView
#property (nonatomic, copy) void (^blockCalled)(NSDictionary *dict);
#end
// A.m
#implementation A
- (instancetype) initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleNotification:) name:#"kNotification" object:nil];
}
return self;
}
- (void)handleNotification:(NSNotification *)notification {
// never just call `blockCalled`; always check to see if not null
if (self.blockCalled) {
// everything is good, so let's call the block
self.blockCalled(notification.userInfo);
}
}
- (void)dealloc {
// never just `addObserver`; make sure to remove the observer when this is deallocated
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"kNotification" object:nil];
}
#end
// B.h
#interface B : UIView
#property (nonatomic, copy) void (^blockCalled)(NSDictionary *dict);
#property (nonatomic, strong) A *aInstance;
#end
// B.m
#implementation B
- (void) someMethod {
// !!!!! why this crashes the app when blockCalled on aInstance is called:
[self.aInstance setBlockCalled:self.blockCalled];
// but this does not crash when the same happens
__weak typeof (self) weakSelf = self;
self.aInstance.blockCalled = ^(NSDictionary *dict) {
__strong typeof (self) strongSelf = weakSelf;
// again, never just call `blockCalled`; always check to see if not null
if (strongSelf.blockCalled) {
strongSelf.blockCalled(dict);
}
};
}
#end
Bottom line, when I fix the two obvious sources of crashes (failing to remove observer and failing to check to make sure the block was non-nil before calling it), and test it with logical scenario, it seems to work fine:
- (void)viewDidLoad {
[super viewDidLoad];
B *b = [[B alloc] init];
b.blockCalled = ^(NSDictionary *dict) {
NSLog(#"%#", dict);
};
A *a = [[A alloc] init];
b.aInstance = a;
[b someMethod];
[self.view addSubview:a];
[self.view addSubview:b];
NSDictionary *dict = #{#"foo": #"bar", #"baz": #"qux"};
[[NSNotificationCenter defaultCenter] postNotificationName:#"kNotification" object:nil userInfo:dict];
}
So, assuming one of these two issues wasn't the source of the problem, you must provide MCVE that we can use to reproduce the crash you describe.

Simple custom delegate not getting called

Let's see if you guys can find the error here... because I'm really stuck right now.
DBManager.h
#protocol DBManagerDelegate <NSObject>
#optional
- (void) managerDidFinishUpdating:(id)controller;
#end
#interface DBManager : NSObject
#property (strong, nonatomic) id <DBManagerDelegate> delegate;
- (id) init;
#end
DBManager.m
#import "DBManager.h"
#implementation DBManager
- (id)init {
self = [super init];
if (self) {
[[self delegate] managerDidFinishUpdating:self];
}
return self;
}
UIViewController.h
#import <UIKit/UIKit.h>
#import "DBManager.h"
#interface DBViewController : UIViewController <DBManagerDelegate>
#property (nonatomic, retain) DBManager *manager;
#end
UIViewController.m
#import "DBViewController.h"
#implementation DBViewController
- (void)viewDidLoad
{
[super viewDidLoad];
_manager = [[DBMataroManager alloc] init];
[_manager setDelegate:self];
}
- (void)managerDidFinishUpdating:(id)controller {
NSLog(#"Manager did finish");
}
#end
You should write like this,
- (id)initWithDelegate:(id)delegate {
self = [super init];
if (self) {
_delegate = delegate;
[_delegate managerDidFinishUpdating:self];
}
return self;
}
//Call it as follow
_manager = [[DBManager alloc] initWithDelegate:self];
[_manager setDelegate:self];
The delegate method managerDidFinishUpdating: is called within init. You set yourself as the delegate after calling init.
Maybe an initWithDelegate: method would help.
- (instancetype)initWithDelegate:(id< DBManagerDelegate>)delegate {
self = [super init];
if (self) {
self.delegate = delegate;
[self.delegate managerDidFinishUpdating:self];
}
return self;
}
You are trying to use the delegate in the init, but you have not set it until the next statement. The only way to do this would be to set the delegate as part of you init statement.
-(id)initWithDelegate:(id<DBManageDelegate>)delegate {
if ((self = [super init])) {
self.delegate = delegate;
if (delegate && [delegate respondsToSelector:#selector(managerDidFinishUpdating:)]) {
[delegate performSelector:#selector(managerDidFinishUpdating:) withObject:self];
}
}
return self;
}
But if you plan to do some additional things before the manager is updated I would suggest you move all of that outside of the init, perhaps into an updateManager function like this
-(void)updateManager {
// Do the stuff that updates your manager here
if (delegate && [delegate respondsToSelector:#selector(managerDidFinishUpdating:)]) {
[delegate performSelector:#selector(managerDidFinishUpdating:) withObject:self];
}
}
..later in your app
_manager = [[DBMataroManager alloc] init];
[_manager setDelegate:self];
[_manager updateManager];

Performing method on another UIViewController

I am using following code to set the text of a UILabel in SViewController, From another ViewController FViewController
SViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"SViewController"];
[vc setTextForLabel:[NSNumber numberWithInt:1];];
[self.navigationController pushViewController:vc animated:YES];
The setTextForLabel method:
-(void) setTextForLabel: (NSNumber *)text {
textLabel = [[UILabel alloc] init];
[textLabel setText:[NSString stringWithFormat:#"%#",[text stringValue]]];
NSLog(#"The text is %#", textLabel.text);
}
I have declared the property for textLabel in .h file is as follows:
#property (nonatomic, retain) IBOutlet UILabel *textLabel;
And I have also set the IBOutlet in storyboard.
Now in console I see "The text is 1", but the UI is not showing anything!
What am I doing wrong?
remove this line
textLabel = [[UILabel alloc] init];
Also, you need to link the UILabel on interface to your #property (nonatomic, retain) IBOutlet UILabel *textLabel;
Or you may add this label manually to view
textLabel = [[UILabel alloc] init];
[textLabel setText:[NSString stringWithFormat:#"%#",[text stringValue]]];
[self.view addSubView:textLabel];
First of all go put
[vc setTextForLabel:[NSNumber numberWithInt:1]];
after you push the vc.
In your xib/storyboard if your label has something written in it, remove it, leave it blank.
If that doesn't work ... leave the setText method before the push one (as you wrote it initialy) and create a NSNumber member that will hold your value and apply it in viewDidAppear like so
-(void) setTextForLabel: (NSNumber *)text{
numberValue = text; // declared in .h as NSNumber *numberValue (only make it a property if you need acces to it from outside your class)
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated]; //this is important
[textLabel setText:[NSString stringWithFormat:#"%#",[numberValue stringValue]]];
}
Your problem is linked to the view lifecycle of SViewController. When you call [.. setTextForLabel] you don't know if the controller has loaded its views (And thus your UILabel have a good chance of not being created yet)
What you should do is use a NSString property on your controller, set it with the desire text and assigned it to the UILabel in the viewDidLoad of SViewController.
in SViewController.h
#propery (strong, nonatomic) NSString *textToDisplay;
in SViewController.m
-(void)viewDidLoad
{
[super viewDidLoad];
// Here you know all Outlet are loaded and connected
textLabel.text = textToDisplay
}
I did like this
#import "FViewController.h"
#import "SViewController.h"
#interface FViewController ()
#end
#implementation FViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (IBAction)do:(id)sender
{
SViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"SViewController"];
[self.navigationController pushViewController:vc animated:YES];
[vc setTextForLabel:[NSNumber numberWithInt:1]];
}
// in SViewController.h
#import <UIKit/UIKit.h>
#interface SViewController : UIViewController
#property (weak, nonatomic) IBOutlet UILabel *label;
-(void) setTextForLabel: (NSNumber *)text;
#end
// SViewController.m file
#import "SViewController.h"
#interface SViewController ()
#end
#implementation SViewController
#synthesize label;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
}
-(void) setTextForLabel: (NSNumber *)text{
// label.text = [text stringValue];
NSLog(#"%#",label);
[label setText:[text stringValue]];
}
#end
also check your label properties in storyboard are properly set

retain array data outside of method objective-c

I have an array, players, with two strings inside it: player1 and player2. Here is my .h file:
#import <UIKit/UIKit.h>
#interface hardOne : UIViewController {
UISwitch *hard1ON;
NSMutableArray *players;
NSString *player1, *player2;
}
#property (nonatomic, retain) IBOutlet UISwitch *hard1ON;
#property (nonatomic) BOOL switchState;
#property (nonatomic, retain) NSMutableArray *players;
- (IBAction) switchValueChanged;
#end
The array is initialized in the viewDidLoad then the data is entered into that array in two IBActions in my .m file:
#import "hardOne.h"
#interface hardOne () <UITextFieldDelegate>
#property (nonatomic, strong) IBOutlet UITextField *textFieldOne;
#property (nonatomic, strong) IBOutlet UITextField *textFieldTwo;
#end
#implementation hardOne
#synthesize hard1ON;
#synthesize players;
#synthesize textFieldOne;
#synthesize textFieldTwo;
BOOL switchState;
int counter = 0;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
[hard1ON setOn:switchState animated:NO];
//read player names to user defaults
[textFieldOne setText:[[NSUserDefaults standardUserDefaults] stringForKey:#"player1"]];
[textFieldTwo setText:[[NSUserDefaults standardUserDefaults] stringForKey:#"player2"]];
self.players = [[NSMutableArray alloc] init];
NSLog(#"%#",self.players);
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction) switchValueChanged
{
counter += 1;
if (counter % 2 == 0) {
switchState = 0;
} else {
switchState = 1;
}
if (hard1ON.on) {
[[NSNotificationCenter defaultCenter] postNotificationName:#"theChange" object:nil];
} else {
[[NSNotificationCenter defaultCenter] postNotificationName:#"theChange2" object:nil];
}
}
- (IBAction) returnKey1
{
player1 = [textFieldOne text];
[self.players addObject:(player1)];
//set player1's name to user defaults
[[NSUserDefaults standardUserDefaults] setValue:[textFieldOne text] forKey:#"player1"];
}
- (IBAction) returnKey2
{
player2 = [textFieldTwo text];
[self.players addObject:(player2)];
//set player2's name to user defaults
[[NSUserDefaults standardUserDefaults] setValue:[textFieldTwo text] forKey:#"player2"];
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
return NO;
}
#end
If I use NSLog in the second IBAction, once it is complete, the array is correctly displayed in the console with the strings player1 and player2, however if I try to use the array anywhere else it is null. Could anyone point me in the right direction?
You've got two definitions for players.
One is a property. It's never initialized and so it's null. You use it as self.players and backed by the instance variable _players.
One is an instance variable. It's initialized in viewDidLoad. It's not nil.
This is almost surely a mistake.
I would try adding the array as a private instance variable i.e add it to the .m file in the #interface with
NSMutableArray *players;
then you should be able to access the array just by using "players" instead of self.players. This should be then be available throughought the whole of your class. If this doesn't work then I would say the problem doesn't lie within the scope of your variable but rather the with some other code.

Resources