I have ARC enabled so I am unsure as to why my reference is null.
My view controller instantiates a UIView ‘theGrid’ as soon as the view is loaded.
Later I have switch inside another class (MyOtherClass) that calls the UIViewContoller - (void) updateTheGrid:(id)sender method, that method is called as per the NSLog, but when I output the UIView to see if it is there, its returns null.
What am I doing wrong? It was my impression that ARC keeps up with everything. I feel like my trouble is coming from mm "MyOtherClass" when I ViewController * vc = [[ViewController alloc] init]; because I feel like that is just creating a new instance. But if that is the case, how am i suppose to reference the old instance and call the method?
NSLOG OUTPUT
[28853:c07] Intial Grid: <GridView: 0x8e423b0; frame = (0 0; 768 1024); layer = <CALayer: 0x8e43780>>
[28853:c07] Update The Grid (null)
GridView.h
#import <UIKit/UIKit.h>
#interface GridView : UIView
- (void) gridUpdated;
#end
GridView.m
#import "GridView.h"
#implementation GridView
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code
NSLog(#"initWithFrame");
}
return self;
}
- (void)drawRect:(CGRect)rect{
NSLog(#"Grid Draw Rect");
}
- (void) gridUpdated {
NSLog(#"GRID VIEW.m : Grid update called");
[self setNeedsDisplay];
}
#end
ViewController.h
#import <UIKit/UIKit.h>
#import "GridView.h"
#interface ViewController : UIViewController {
GridView *theGrid;
}
#property (strong, retain) GridView * theGrid;
- (void) updateTheGrid : (id) sender;
#end
ViewController.m
#import "ViewController.h"
#import "GridView.h"
#interface ViewController () {}
#end
#implementation ViewController
#synthesize theGrid;
- (void)viewDidLoad {
[super viewDidLoad];
//draw the grid
theGrid = [[GridView alloc] initWithFrame:self.view.frame];
NSLog(#"Intial Grid: %#", theGrid);
[self.view addSubview:theGrid];
}
- (void) updateTheGrid : (id) sender{
NSLog(#"Update The Grid %#", theGrid);
[theGrid gridUpdated];
}
#end
MyOtherClass.m
- (void) mySwitch : (id) sender {
ViewController * vc = [[ViewController alloc] init];
[vc updateTheGrid:sender];
}
Do not allocate ViewController object again in your MyOtherClass.m because it will create an new instance of ViewController and your previous objects which holds ViewController wil get disposed including theGrid.
So please declare a weak property of ViewController inside the MyOtherClass.m and assign it while allocating MyOtherClass.m
Example:
ViewController class
moc = [[MyOtherClass alloc] initWithFrame:self.view.frame];
moc.vc = self;
MyOtherClass.h
#property(nonatomic,weak) ViewController *vc;
MyOtherClass.m
- (void) mySwitch : (id) sender {
[self.vc updateTheGrid:sender];
}
Note:Take care about the forward declarations :)
Related
I am trying to update the contents of an NSTextView that is connected to myViewController as a referencing outlet to the Files Owner which is the subclass myViewController.
When I use an IBAction from a button, or use the viewDidLoad method of the controller, I can update the text fine. However, when I try run the method from another class (referred to in this example as anotherViewController), it runs the method, but the textview does not change.
myViewController.h:
#import <Cocoa/Cocoa.h>
#import "anotherViewController.h"
#interface myViewController : NSViewController { }
#property (unsafe_unretained) IBOutlet NSTextView *outText;
#property (weak) IBOutlet NSButton *updateMeButton;
- (void)updateTextView:(NSString *)argText;
- (void)updateTextViewWithoutArg;
#end
myViewController.m:
#import "myViewController.h"
#interface myViewController ()
#end
#implementation myViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.outText.string = #"I work successfully";
}
- (IBAction)updateMeButton:(id)sender {
self.outText.string = #"I am updated text! I also work!";
}
- (void)updateTextView:(NSString *)argText {
self.outText.string = #"I don't make it to the NSTextView :(";
NSLog(#"Should have updated text view");
}
- (void)updateTextViewWithoutArg {
self.outText.string = #"I don't make it to the NSTextView :(";
NSLog(#"Should have updated text view");
}
#end
In anotherViewController.m , which has all the relevant imports, I call this:
myViewController *viewtask = [[myViewController alloc] init];
[viewtask updateTextViewWithoutArg];
Nothing happens. The method runs and logs that it should have updated, but no text updates. I have tried many different approaches, including textstorage and scrollrange methods, they all work the already working sections, but make no difference in the sections not working.
I've also tried just for fun:
myViewController *viewtask;
[viewtask updateTextViewWithoutArg];
Also using the instance variable _outText
Also using [self.outText setString:#"string"];
Also using [_outText setString:#"string"];
Again, they work but only in the already working sections.
This should be simple but isn't logical to me. In swift all I need to do is
self.outText.string = "I update whenever I'm called!"
Views you create in Interface Builder are lazily created, so if you access them before viewDidLoad is called they are nil.
If your case, calling
myViewController *viewtask = [[myViewController alloc] init];
does not cause the views to be created so when you call
[viewtask updateTextViewWithoutArg];
self.outText is nil.
You can see that this is what is happening by updating your code as below:
- (void)updateTextView:(NSString *)argText {
NSAssert(self.outText != nil, #"self.outText must not be nil");
self.outText.string = #"I don't make it to the NSTextView :(";
NSLog(#"Should have updated text view");
}
you should see the assert fire.
I appear to have found a solution by making myViewController a singleton class and using sharedInstance. For this particlar app, myViewController is a debug output window and will never need to be placed in another view.
I won't accept this answer yet, as it's not the best one I'm sure. There may still be a proper solution presented that allows finding the applicable myViewController instance, and modifying the outText property attached to it. Using this singleton makes subclassing tedious as I would have to make a new class for every instance if I wanted to be able to address say 10 View Controllers.
Anyway - the way I've been able to satisfy my simple requirement:
myViewController.h:
#import <Cocoa/Cocoa.h>
#import "anotherViewController.h"
#interface myViewController : NSViewController { }
#property (unsafe_unretained) IBOutlet NSTextView *outText;
#property (weak) IBOutlet NSButton *updateMeButton;
- (void)updateTextView:(NSString *)argText;
- (void)updateTextViewWithoutArg;
+ (id)sharedInstance;
#end
myViewController.m:
#import "myViewController.h"
#interface myViewController ()
#end
#implementation myViewController
static myViewController *sharedInstance = nil;
+ (myViewController *)sharedInstance {
static myViewController *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[myViewController alloc] init];
});
return sharedInstance;
}
- (void)viewDidLoad {
[super viewDidLoad];
sharedInstance = self;
}
- (void)viewDidUnload {
sharedInstance = nil;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.outText.string = #"I work successfully";
}
- (IBAction)updateMeButton:(id)sender {
sharedInstance.outText.string = #"Button Pressed";
}
- (void)updateTextView:(NSString *)argText {
sharedInstance.outText.string = argText;
}
- (void)updateTextViewWithoutArg {
sharedInstance.outText.string = #"I make it to the TextView now";
}
#end
Now when I use this code from within anotherViewController.m it updates the right instance:
[myViewController.sharedInstance updateTextView:#"Updating with this string"];
I'm quite new to Mac programming (not to Objective C).
I'm developing a small application, that shows some data and opens a second window on button press.
In the second window is a textfield and a submit button. If the submit button is pressed, the window should close + the value of the textfield needs to be passed to the first window.
I think the best method for that is a simple Delegate. I tried that but i can't change the label in the first window using the second window..
The delegate however seems to work as i can call methods from the other class and send data to it. It just won't change the label.
As this is my first try on Delegates, im pretty sure I've done something stupid here^^
or is there a better solution? Can't be to complicated to change a label from an second window.. right?
ViewController.h (FirstController)
#import <Cocoa/Cocoa.h>
#class ViewController;
#protocol ViewControllerDelegate
-(void)sayHello:(ViewController *)ViewController;
#end
#interface ViewController : NSViewController
{
IBOutlet NSTextField *txtlabel;
}
#property (nonatomic, assign) id delegate;
-(void)helloDelegate;
-(void)reciveVar:(NSString*)strvar;
#end
ViewController.m (FirstController)
#import "ViewController.h"
#implementation ViewController
#synthesize delegate;
-(id)init {
self = [super init];
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
txtlabel.stringValue=#"TEST";
}
-(void)helloDelegate
{
[delegate sayHello:self];
}
-(void)reciveVar:(NSString*)strvar
{
NSLog(#"recived: %#", strvar);
txtlabel.stringValue=strvar; // DOSENT WORK!!
}
#end
secondController.h
#import <Cocoa/Cocoa.h>
#import "ViewController.h"
#interface secondController : NSViewController <ViewControllerDelegate>
{
IBOutlet NSTextField *txtfield;
}
-(IBAction)submit:(id)sender;
#end
secondController.m
#import "firstController.h"
#implementation secondController
-(void)viewDidLoad
{
[super viewDidLoad];
ViewController *custom = [[ViewController alloc] init];
// assign delegate
custom.delegate = self;
[custom helloDelegate];
}
-(void)sayHello:(ViewController *)ViewController
{
NSLog(#"Hiya!");
}
-(IBAction)submit:(id)sender
{
NSString *txtval= txtfield.stringValue;
NSLog(#"submit: %#", txtval);
ViewController *custom = [[ViewController alloc] init];
// assign delegate
custom.delegate = self;
[custom reciveVar:txtval];
}
#end
LOG Output:
Hiya!
submit: test
recived: test
(so i guess the delegate works..)
SOLVED. (Thanks to Phillip Mills)
NSNotification is way simpler and efficient than Delegates in this case.
ViewController.m
[...]
- (void)viewDidLoad
{
[super viewDidLoad];
txtlabel.stringValue=#"TEST";
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(handleUpdatedData:)
name:#"DataUpdated"
object:nil];
}
-(void)handleUpdatedData:(NSNotification *)notification
{
NSLog(#"recieved %#", notification);
txtlabel.stringValue=[notification object];
}
secondController.m
-(IBAction)submit:(id)sender
{
NSString *txtval= txtfield.stringValue;
NSLog(#"submit: %#", txtval);
[[NSNotificationCenter defaultCenter] postNotificationName:#"DataUpdated"
object:txtval];
}
First of all, let me say that I am new to Objective C.
I'm basically trying to pass the originalPriceOnGraph variable from ViewController (UIViewController) to the original variable from GraphView (UIView). However, I keep getting 0.00 when I try and display original. I don't get what exactly is the problem. Here's some of my code:
GraphView.h
#interface GraphView : UIView
#property (nonatomic) double original;
#end
GraphView.m
#implementation GraphView
#synthesize original;
- (id)initWithFrame:(CGRect)frame
{
//some code here
}
- (void)drawRect:(CGRect)rect;
{
NSLog(#"%.2f", original);
//some more code here
}
#end
ViewController.m
#interface OtherViewController ()
#end
#implementation OtherViewController
#synthesize originalPriceOnGraph;
#synthesize graph;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
originalPriceOnGraph = 20.00;
graph = [[GraphView alloc] init];
graph.original = originalPriceOnGraph;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
ViewController.h
#interface OtherViewController : UIViewController
#property (strong, nonatomic) GraphView *graph;
#property (nonatomic) double originalPriceOnGraph;
#end
Thank you in advance!
EDIT: I was able to solve this by creating an IBOutlet between the OtherViewController and GraphView. I also got rid of the alloc init statement for GraphView in ViewController.m. Thank you all for your suggestions!
Are you sure the GraphView's drawRect: method isn't getting called before you set its 'original' property?
If so, try initializing any instance of a GraphView with a default value for original.
In GraphView.h:
-(id)initWithOriginal:(double)original;
In GraphView.m:
-(id)initWithOriginal:(double)original
{
self = [super init];
if (self) {
[self setOriginal:original];
}
return self;
}
In ViewController.m:
-(void)viewDidLoad
{
[super viewDidLoad];
originalPriceOnGraph = 20.00;
[self setGraph:[[GraphView alloc] initWithOriginal:originalPriceOnGraph]];
}
use :
self.graph = [[GraphView alloc] init];
self.graph.original = originalPriceOnGraph;
How to realize the label text dynamic change? For example,
labe.text = #"1";
the second is the :
label.text = #"2";
You can do this by extending UILabel class.
MyLable.h
#import <UIKit/UIKit.h>
#protocol MyLabelDelegate;
#interface MYLabel : UILabel
#property(nonatomic, unsafe_unretained) id<MyLabelDelegate> delegate;
#end
#protocol MyLabelDelegate <NSObject>
-(void) label:(UILabel *) label didChangeText:(NSString *) string;
#end
My label.m
#implementation MYLabel
#synthesize delegate = _delegate;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
- (void)setText:(NSString *)text{
[super setText:text];
if([self.delegate respondsToSelector:#selector(label:didChangeText:)]){
[_delegate label:self didChangeText:text];
}
}
#end
and you can implement MyLabel delegate in your view controller if want to detect change there
I know this is just a fundamental question but still somewhere I am missing something, I am playing with passing data to a textView from another class. For this I have created two classes one with xib file (ViewController) and another without(secondVC).
What I am trying to do is that I have a textview in ViewController class and wanted to pass the data to this textView from secondVC. This is how I am doing.
//ViewController.h
#import <UIKit/UIKit.h>
#import "secondVC.h"
#interface ViewController : UIViewController{
IBOutlet UITextView *textView;
}
#property (nonatomic, retain) UITextView *textView;
- (IBAction)go:(id)sender;
#end
//ViewController.m
- (IBAction)go:(id)sender{
secondVC *sec = [[secondVC alloc] init];
[sec print];
}
//secondVC.h
#import <UIKit/UIKit.h>
#import "ViewController.h"
#interface secondVC : UIViewController
- (void)print;
#end
//secondVC.m
- (void)print{
NSString *printThis = #"This works";
ViewController *vc = [[ViewController alloc] init];
[vc.textView setText:printThis];
//vc.textView.text = printThis //Tried both
}
Any suggestions would be appreciated.
Thanks
you can do like this :
//ViewController.h
#import <UIKit/UIKit.h>
#import "secondVC.h"
#interface ViewController : UIViewController{
IBOutlet UITextView *textView;
}
#property (nonatomic, retain) UITextView *textView;
- (IBAction)go:(id)sender;
#end
//ViewController.m
- (IBAction)go:(id)sender{
secondVC *sec = [[secondVC alloc] init];
sec.viewController = self;
[sec print];
}
//secondVC.h
#import <UIKit/UIKit.h>
#import "ViewController.h"
#interface secondVC : UIViewController {
ViewController *viewController;
}
#property(nonatomic, retain)ViewController *viewController;
- (void)print;
#end
//secondVC.m
#synthesize viewController;
- (void)print{
NSString *printThis = #"This works";
self.viewController.textView.text = printThis ;
}
Try with protocol... if you want to send string from textView(child) to other ViewController(parent)
You need a delegate method that is fired from the SecondVC and handled in the first one (ViewController).
There are a few issues here:
You've got a ViewController creating a new secondVC and sending it a print message. That's okay, but the implementation of -print creates a different instance of ViewController and tries to set the text of it's textView property. That's clearly not what you want -- you should instead be sending the text back to the original instance of ViewController.
That second instance of ViewController very likely has its textView property set to nil since textView is an outlet, but you haven't loaded its view from the .xib.
It's really not nice for one view controller to mess with the views of another view controller. The secondVC should be giving the text to the original ViewController object, not trying to set the text of one of its views.
To facilitate communication from secondVC to ViewController, give secondVC a property to keep track of the original ViewController. The usual thing to do here is to define a delegate protocol for secondVC and implement that protocol in ViewController. When ViewController creates secondVC, it sets the delegate of secondVC to itself. That gives secondVC a pointer to its delegate (it shouldn't care whether its a ViewController or some other kind of object, as long as the delegate implements the right methods).
.h file:
#import <UIKit/UIKit.h>
#protocol StringDelegate <NSObject>
-(void)getArrayOfStrings:(NSMutableArray*)strArray;
#end
#interface WWSettings : UIViewController{
}
#property(nonatomic,assign)id<StringDelegate>delegate;
#end
.m file:
#import "WWSettings.h"
#implementation WWSettings
#synthesize delegate;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
-(void)blablablaFunction{
[delegate getArrayOfStrings:yourArray];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
}
#end
if you dont understand how it works .. ask ! i'll make my best to help you )
your secondVC
#import <UIKit/UIKit.h>
#import "WWSettings.h"
#interface secondVC : UIViewController<StringDelegate>{
WWSettings *obj;
}
#end
and .m file :
#import "secondVC.h"
#implementation secondVC
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
-(void)getArrayOfStrings:(NSMutableArray *)strArray{
// here you get your array !!! it's a delegate function made by you in child viewController;
}
- (void)viewDidLoad
{
obj = [[WWSettings alloc]init];
[obj setDelegate:self];
[super viewDidLoad];
// Do any additional setup after loading the view.
}
first VC .h file :
#import <UIKit/UIKit.h>
#protocol textViewChildDelegate <NSObject>
-(void)getStrings:(NSString*)string;
#end
#interface textViewChild : UIViewController<UITextViewDelegate>{
UITextView *textView;
}
#property(nonatomic,assign)id<textViewChildDelegate>delegate;
#end
.m file:
#import "textViewChild.h"
#implementation textViewChild
#synthesize delegate;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
-(void)myWorkingMethod{
// get string from textView
[delegate getStrings:textView.text];
}
- (void)viewDidLoad
{
textView = [[UITextView alloc]initWithFrame:CGRectMake(0, 240, 320, 240)];
[super viewDidLoad];
// Do any additional setup after loading the view.
}
Now go to secondVC .h:
#import <UIKit/UIKit.h>
#import "textViewChild.h"
#interface TextViewViewController : UIViewController<textViewChildDelegate>{
UITextView * myfirstTextView;
}
#end
and to .m file:
#import "TextViewViewController.h"
#implementation TextViewViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
-(void)getStrings:(NSString *)string{
myfirstTextView.text = string; // finally we get string from child view controller
}
- (void)viewDidUnload
{
myfirstTextView = [[UITextView alloc]init];
[super viewDidUnload];
// Release any retained subviews of the main view.
}