View's dataSource property set to nil after I set it - objective-c

New to iOS/Objective-C here. I've spent a lot of time trying to figure this out, but just can't manage it. Here's what's happening:
Click a 'Graph' button on my RootViewController's view
GraphViewController takes over, has a graphView property
I set the graphView's dataSource property to self
Checking self.graphView.dataSource with NSLog confirms that it does indeed point to self
When GraphView's drawRect: is called, self.dataSource is set to (null), where I expected it to point to the GraphViewController object
To summarise: I'm instantiating the graphView property in the view controller, then setting its dataSource, but by the time the view's drawRect: is called the dataSource is no longer set.
GraphViewController.m:
#import "GraphViewController.h"
#import "GraphView.h"
#implementation GraphViewController
#synthesize graphView = _graphView;
#synthesize program = _program;
- (GraphView *)graphView {
if(!_graphView) {
_graphView = [[GraphView alloc] init];
[_graphView setDataSource:(id)self];
}
return _graphView;
}
#end
GraphViewController.h:
#import <UIKit/UIKit.h>
#import "GraphView.h"
#interface GraphViewController : UIViewController
#property (strong, nonatomic) IBOutlet GraphView *graphView;
#end
GraphView.m:
#import "GraphView.h"
#implementation GraphView
#synthesize dataSource = _dataSource;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
NSLog(#"initWithFrame called, self.dataSource=%#", self.dataSource);
}
return self;
}
- (void)drawRect:(CGRect)rect
{
NSLog(#"drawRect:");
NSLog(#"\tself=%#", self);
NSLog(#"\tself.dataSource=%#", self.dataSource); // is (null), shouldn't be
[self.dataSource programToGraph];
}
#end
GraphView.h:
#import <UIKit/UIKit.h>
#class GraphView;
#protocol GraphViewDataSource
- (id)programToGraph;
#end
#interface GraphView : UIView
#property (nonatomic, weak) IBOutlet id <GraphViewDataSource> dataSource;
#end

So it looks like the GraphViewController instance is being deallocated, which will nil out the dataSource property.
So you should go back and look at how you are creating and managing that GraphViewController. A common mistake is to create a view controller like that, then borrow its view and throw it into some other view controller's view hierarchy, and then let the original view controller just go away.
I would look at that, and if you don't see it there, post the code where you create and present the GraphViewController.
Oh, and where are you calling initWithFrame: from?? It almost looks like you could also have two different instances of GraphView floating around. Try logging 'self' along with self.datasource to check that also.

Related

Objective-c - control outlet from other class

I just like to play with coding for a hobby, so probably a noob question;
I have a simple storyboard for MacOS with 2 views. Both have there own classes (main class and subclass). How can I control a outlet in the subclass from the main class?
for example
I have a button (IBAction) in the mainclass and a textfield (IBOutlet) in the subclass. I want to set the stringvalue for the textfield with a click on the button in main.
I have searched a lot last days but just don't get it. (or just need a push in the right direction)
EDIT after JingJingTao's answer:
I used the control-drag function to open the second window.
I tried the code JingJingTao gives, but the textfield doesn't respond to the action.
My classes look like this now:
ViewController.h
#import <Cocoa/Cocoa.h>
#interface ViewController : NSViewController
- (IBAction)newText:(id)sender;
#end
ViewController.m
#import "ViewController.h"
#import "ViewController2.h"
#interface ViewController ()
#property (nonatomic) ViewController2 *subclass;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)setRepresentedObject:(id)representedObject {
[super setRepresentedObject:representedObject];
}
- (void)newText:(id)sender {
self.subclass.textField.stringValue = #"button pressed";
}
#end
ViewController2.h
#import "ViewController.h"
#interface ViewController2 : ViewController
#property (nonatomic) IBOutlet NSTextField *textField;
#end
ViewController2.m
#import "ViewController2.h"
#interface ViewController2 ()
#end
#implementation ViewController2
- (void)viewDidLoad {
[super viewDidLoad];
}
#end
Update:
I've attached two screenshots of what it looks like in the storyboard for the first suggestion,
1) Add a view to your ViewController, set the class at the top right to 'YourView', 'YourView' is a just an NSView, add a textfield to it and hook it up.
2) Add YourView as a property to your ViewController, i.e. #property (nonatomic) IBOutlet NSView *yourView; and hook it up.
Let me know if there are any issues.
You just need to put the textfield in the public interface of your subclass, so you can access it in your main class, although it does sound like you're using inheritance and I don't think you need to but that's another topic :D.
Example:
In MainClassViewController.m
#interface MainClassViewController ()
#propert (nonatomic) Subclass *subclass;
#end
#implementation MainClassViewController
// I guess you already add your subclass to the main viewcontroller because they display on the same screen.
- (void)yourButtonTapMethod {
self.subclass.textfield.text = #"Your value";
}
In Subclass.h
#interface Subclass : NSObject
#property (nonatomic) IBOutlet UITextfield *textfield;
I use Cocoa Touch instead of Cocoa, so maybe it's NSTextfield for you. Please let me know if this does not answer your question, good luck.

How to correctly implement a NSWindowController subclass with xib-file

I am trying to implement a NSWindowController subclass with new xib-file, I read up in lots of books, and researched on StackOverflow, but none of the steps provided made my window show, nor did the subclass code get executed. The new xib-file has its File's Owner set to "LogNavigatorController" and connections to the window and its contents have been made.
My AppDelegate.h:
#import <Cocoa/Cocoa.h>
#class LogNavigatorWindowController;
#interface AppDelegate : NSObject <NSApplicationDelegate>
{
LogNavigatorWindowController *logsWindowController;
}
#end
My AppDelegate.m:
#import "AppDelegate.h"
#import "LogNavigatorWindowController.h"
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
logsWindowController = [[LogNavigatorWindowController alloc] initWithWindowNibName:#"LogNavigatorWindowController"];
[logsWindowController showWindow:self];
}
#end
My LogNavigatorWindowController.h:
#import <Cocoa/Cocoa.h>
#interface LogNavigatorWindowController : NSWindowController
{
NSArray *directoryList1;
NSArray *directoryList2;
NSMutableArray *directoryList;
NSMutableArray *filePaths1;
NSMutableArray *filePaths2;
}
#property (assign) IBOutlet NSWindow *window;
#property (weak) IBOutlet NSTableView *logsTableView;
#property (unsafe_unretained) IBOutlet NSTextView *logsTextView;
#property (assign) IBOutlet NSArrayController *LogListController;
#property (retain) NSMutableArray *logsArray;
- (void) myDirectoryLogFunction;
#end
My LogNavigatorController.m:
#import "LogNavigatorWindowController.h"
#interface LogNavigatorWindowController ()
#end
#implementation LogNavigatorWindowController
#synthesize logsTableView;
#synthesize logsTextView;
#synthesize window;
- (id)init
{
self = [super initWithWindowNibName:#"LogNavigatorWindowController"];
[self loadWindow];
[self showWindow:#"Log Navigator"];
[self.window makeKeyAndOrderFront:nil];
if (self)
{
// Initialization code here.
[self myDirectoryLogFunction];
}
return self;
}
- (void)windowDidLoad
{
[super windowDidLoad];
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
}
- (void) myDirectoryLogFunction
{
NSLog(#"Code execution test successful");
}
#end
You don't need to create the window property since it is already available for NSWindowController subclasses. Maybe that causes the problem.
Also your init method contains a lot of code that doesn't belong there. Remove
[self loadWindow];
[self showWindow:#"Log Navigator"];
[self.window makeKeyAndOrderFront:nil];
as well as replace
self = [super initWithWindowNibName:#"LogNavigatorWindowController"];
with
self = [super init];
You may want to remove the init method at all, since you don't need it in your case.
and move
[self myDirectoryLogFunction];
to the windowDidLoad method.
Also always check that the code for instantiating the window controller (in your case from the app delegates didFinishLaunching: ) is called. Sometimes it helps to create a new project and test there, if you may have changed too much within the original project and by accident removed delegate connections or similar.

NSTextView string is null when calling from an other class

I'm a newbie in Cocoa
I have a function in a class call TextSaver.m :
- (void) save {
TheNotes *myNote = [[TheNotes alloc]init];
myNote.theText = [theTextView string];
NSLog(#"%#",myNote.theText);
[NSKeyedArchiver archiveRootObject:myNote.theText toFile:#"..."];
}
And I'm calling it from the AppDelegate with applicationWillTerminate :
- (void)applicationWillTerminate:(NSNotification *)notification{
[theTextSaver save];
}
But NSLog(#"%#",myNote.theText); results null... Like NSLog(#"%#",theTextView);. Which means when I call the function I can't access theTextView.
I've already try to call this function in the TextSaver.m class with a -(IBAction) and it worked!
Hope you can help me !
EDIT
The TextSaver is created with an #import TextSaver.h and in the appInterface
TextSaver *theTextSaver;
EDIT 2
I rewrite the code to make it simpler :
AppDelegate.h :
#import <Cocoa/Cocoa.h>
#import "TheNotes.h"
#interface AppDelegate : NSObject <NSApplicationDelegate>{
TheNotes *myNote;
}
#property (copy) TheNotes *myNote;
#property (assign) IBOutlet NSWindow *window;
#end
AppDelegate.m :
#import "AppDelegate.h"
#implementation AppDelegate
#synthesize myNote;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification{
myNote = [[TheNotes alloc]init]; //Do I need to put it in -(id) init ?
}
- (void)applicationWillTerminate:(NSNotification *)notification{
[myNote save];
}
#end
TheNotes.h :
#import <Foundation/Foundation.h>
#interface TheNotes : NSObject {
NSString *theText;
IBOutlet NSTextView *theTextView;// Do I need to alloc memory ?
}
-(void) save;
#property (copy) NSString *theText;
#end
TheNotes.m :
#implementation TheNotes
#synthesize theText;
- (void) save {
NSLog(#"%#",theTextView.string);// Save is properly called, but this results (null).
[NSKeyedArchiver archiveRootObject:theTextView.string toFile:#"..."];
}
#end
The two questions you need to answer for yourself are:
Why do I expect my TextSaver to know about the text view?
Where do I tell the TextSaver about the text view?
The other possible answer to the first question is “the TextSaver created the text view”, but I'm assuming that's not the case.
So, you need to find where you think you're telling the TextSaver about the text view and make sure that's the case.
If you haven't done anything specific to tell the TextSaver about the text view, but rather are expecting it to just know about it, then that's probably the problem.
As Phillip Mills alluded to in his comment, merely declaring a variable named theTextView does not mean that the TextSaver knows about the text view. The compiler cannot read English: the names you choose are for your own benefit only; the compiler treats them only as identifiers. It does not see “theTextView” and go “oh, that! that's over there; I'll go get it”.
In order for theTextView to actually point to the text view, you need to put the text view there. You do this via assignment. Either expose theTextView as a property and set it from somewhere else, or set it internally within the TextSaver class (after either creating the text view yourself or getting it from another object).
I would make it a property (named simply textView) and set that property from whatever owns both the TextSaver and the text view.
This is a working example of basically what you're trying to achieve, HTH.
There are two classes, AppDelegate and TestViewController. TestViewController has a UITextView, whenever the user presses the home button of the device while editing the UITextView, the - (void)applicationDidEnterBackground:(UIApplication *)application method of the AppDelegate is called and the note is printed to the console (here you could save the note instead).
I use applicationDidEnterBackground because it's what is called when the app goes into background mode.
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
PVSTestViewController *nextScreen = [[PVSTestViewController alloc] init];
self.delegate = nextScreen; // Assign TestViewController as the AppDelegate's delegate.
self.window.rootViewController = nextScreen;
[self.window makeKeyAndVisible];
return YES;
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[self.delegate saveData]; // Called when the user presses the home button.
}
AppDelegate.h
....
#protocol PVSAppDelegateProtocol <NSObject>
- (void)saveData; // Any class that conforms to our protocol must implement this method.
#end
#interface PVSAppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
#property (nonatomic, weak) id<PVSAppDelegateProtocol> delegate; // Here we store the delegate class.
#end
TestViewController.m
#import "PVSTestViewController.h"
#interface PVSTestViewController ()
#property (weak, nonatomic) IBOutlet UITextView *textView;
#end
#implementation PVSTestViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
- (void)saveData
{
// This is called by the AppDelegate when the app goes into the background.
NSLog([NSString stringWithFormat:#"Text is: %#", self.textView.text ]);
}
#end
TestViewController.h
....
#import "PVSAppDelegate.h"
#interface PVSTestViewController : UIViewController <PVSAppDelegateProtocol>
#end

Objective-C message being sent to superclass

I have derived a class SignalView from UIView however when I send a message to my object of type SignalView it gets sent to a UIView and I get error :
-[UIView Initialise]: unrecognized selector sent to instance 0x1b3900
my object also appears as a UIView object in the debug watch window:
m_signalview UIView * 0x001b3900
the relevant code is:
// signalview.h
#import <UIKit/UIKit.h>
#interface SignalView : UIView
{
}
-(void)Initialise;
#end
//signalview.m
#import "SignalView.h"
#interface SignalView ()
#end
#implementation SignalView
-(void)Initialise
{
}
// viewcontroller.h
#import <UIKit/UIKit.h>
#import "SignalView.h"
#interface ViewController : UIViewController
#property (weak, nonatomic) IBOutlet SignalView *m_signalview;
#end
// viewcontroller.m
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize m_signalview;
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"%#", m_signalview);
[m_signalview Initialise];
}
H2C03 is right. its likely not a SignalView. I would check your outlet in your view's nib file in the interface builder inspector make sure it doesn't still say UIView. If it does change it to SignalView:
Should look like this:
Then it's simply an instance of UIView and not a SignalView. You're allocating and initializing UIView when you would need a SignalView instance. When you have something like this in your code:
m_signalView = [[UIView alloc] init];
change it to
m_signalView = [[SignalView alloc] init];
I see that m_signalview is an IBOutlet.
Are you connecting that to a UIView you added in Interface Builder?
If so you might need to set the right class in its properties so that the loader can create an object of the right class.

Calling NSObject superclass method with [self performSelector:#selector]?

I have a Subclass of NSObject in which I want to call IMMEDIATELY (a sort of -(void)viewDidLoad) a method (in this case to load a MkMapView): what's the better way to do this? I think I cant use viewDidLoad, so can I use performSelector?
SubClass.h
#interface Mysubclass : NSObject <MKMapViewDelegate> {
}
SubClass.m (1st alternative)
-(id)init{
self = [super init];
if ( self != nil ) {
// THE CODE TO INITIALIZE MKMAPVIEW
}
return self
}
OR
SubClass.m (2nd alternative)
-(id)init{
[self performSelector:#selector(myMethod)];
return self;
}
-myMethod{
// THE CODE TO INITIALIZE MKMAPVIEW
}
What's the better (or correct) alternative? Its possible to avoid -(id)init? Or everytime I add a subclass, to call a method I have to write it into -(id)init? Thank you!
There is no reason to use -performSelector: in this context. If you want to add a method that initializes the MKMapView when your object is created, call the method from within the if (self) block:
- (id)init
{
self = [super init];
if (self) {
[self setupMapView];
}
return self;
}
- (void)setupMapView
{
// THE CODE TO INITIALIZE MKMAPVIEW
}
It is a matter of personal preference/style whether to have a second method -setupMapView or to simply leave the code for setting up the MKMapView in the if (self) block of the -init method or to break the code off into a second method -setupMapView called from -init.
That being said, it sounds like other things may be off with your setup. Your MKMapView should [most likely] be within a UIViewController subclass (which will probably have an associated XIB), so you will be have access to -viewDidLoad. Note that your UIViewController subclass will serve as the delegate to your MKMapView.
Update 1
In your UIViewController subclass instance (I'll assume you called it ViewController, you should have an IBOutlet to an MKMapView object. Do this in ViewController.h either by (1) adding an instance variable
#interface ViewController : UIViewController
{
IBOutlet MKMapView *myMap;
}
#end
or by (2) adding a property
#interface ViewController : UIViewController
#property (nonatomic, strong, readwrite) IBOutlet MKMapView *myMap;
#end
Now open ViewController.xib in Interface Builder. You should have an MKMapView inside the view. If you don't already, add one from the Object Library. Right click on File's Owner. Locate the row with the item myMap. Drag from the circle on the right end of the row to the MKMapView in the visible view.
Your ViewController class now has an outlet to the MKMapView. You will be able to send messages to the MKMapView subview of your view controllers view after it has been loaded.
You should have a property or an instance variable for your SubClass instance so that it doesn't get destroyed as soon as -viewDidLoad returns. Do this again by either adding an instance variable to ViewController.h
#interface ViewController : UIViewController
{
IBOutlet MKMapView *myMap;
SubClass *istance;
}
#end
or by adding a property
#interface ViewController : UIViewController
#property (nonatomic, strong, readwrite) IBOutlet MKMapView *myMap;
#property (nonatomic, strong, readwrite) SubClass *istance;
#end
Now, in ViewController.m, you need to define -viewDidLoad so that self.istance is set as the delegate of self.myMap. In the comments, I had suggested creating your own initializer -initWithMapView:. If you plan on having SubClass do some extensive set-up of your MKMapView, that makes sense. If you just want SubClass to be the delegate of the MKMapView, there's no need for such a method.
Let's consider both cases:
(1) using a method -[SubClass initWithMapView:]:
In ViewController.m you'll have (within the #implementation of ViewController)
- (void)viewDidLoad
{
self.istance = [[SubClass alloc] initWithMapView:self.myMap];
}
In SubClass.h you'll have (within the #interface of SubClass)
- (id)initWithMapView:(MKMapView *)mapView;
#property (nonatomic, weak, readwrite) MKMapView *mapView;
In SubClass.m you'll have (within the #implementation of SubClass)
- (id)initWithMapView:(MKMapView *)mapView
{
self = [super init];
if (self) {
self.mapView = mapView;
self.mapView.delegate = self;
//more setup of mapView.
}
return self;
}
(2) using -[SubClass init]:
Instead, in ViewController.m you'll have
- (void)viewDidLoad
{
self.istance = [[SubClass alloc] init];
self.myMap.delegate = self.istance;
}