How to access NSViews from other classes - objective-c

Before I start, I am a beginner, so please don't overwhelm me. (even-though I probably did with you sorry) :/
So basically I have 1 main view (NSView) onto which I am loading other views. So my awakeFromNib method works and it loads my start menu view (main menu to a tic tac toe game). On the menu is a single player and a double player button, and depending on which button I click, I would like the program to load further views.
So I have an AppController class which controls which views are loaded through a setViewController method. The issue is that I have not found a way to connect the buttons from my start menu view to the to the AppController class. So I thought if inside the start menu class I create an object of type AppController and then call on the setViewController method when the single or double player button is pressed, it would change the views accordingly, but it turns out it does nothing. However when I call on the setViewController method inside the AppController class, it does work. So I think the issue has to be somewhere with accessing the view from outside its class, but I might be wrong. Any help would be greatly appreciated, I have spent a lot of time trying to figure this out and I have not had any luck with anything I tried. Here is my AppController class and my start
screen class.
AppController.h:
#interface AppController : NSObject
#property (weak) IBOutlet NSView *mainMenu;
#property (strong) NSViewController *mainViewController;
-(void)setViewController:(NSInteger)viewNumber;
#end
AppController.m:
#implementation AppController
#synthesize mainMenu = _mainMenu;
#synthesize mainViewController =_mainViewController;
NSString *const kStartScreen = #"StartScreenViewController";
NSString *const kOnePlayerMenu = #"OnePlayerMenuViewController";
NSString *const kTwoPlayerMenu = #"TwoPlayerMenuViewController";
int test = 0;
enum{
kStartScreenView = 0,
kOnePlayerView,
kTwoPlayerView
};
-(void)awakeFromNib
{
[self setViewController:0];
}
-(void)setViewController:(NSInteger)viewNumber
{
[[_mainViewController view] removeFromSuperview];
if(viewNumber==kStartScreenView)
{
self.mainViewController = [[StartScreenViewController alloc] initWithNibName:
kStartScreen bundle:nil];
}
else if(viewNumber==kOnePlayerView)
{
self.mainViewController = [[OnePlayerMenuViewController alloc] initWithNibName:
kOnePlayerMenu bundle:nil];
}
else if(viewNumber==kTwoPlayerView)
{
self.mainViewController = [[TwoPlayerMenuViewController alloc] initWithNibName:
kTwoPlayerMenu bundle:nil];
}
[_mainMenu addSubview:[_mainViewController view]];
[[_mainViewController view] setFrame:[_mainMenu bounds]];
}
#end
StartScreenViewController.h:
#interface StartScreenViewController : NSViewController
- (IBAction)OnePlayer:(id)sender;
- (IBAction)TwoPlayer:(id)sender;
#end
StartScreenViewController.m:
#interface StartScreenViewController ()
#end
#implementation StartScreenViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self)
{
}
return self;
}
- (IBAction)OnePlayer:(id)sender
{
AppController *appControllerObj = [[AppController alloc] init];
[appControllerObj setViewController:1];
}
- (IBAction)TwoPlayer:(id)sender
{
AppController *appControllerObj = [[AppController alloc] init];
[appControllerObj setViewController:2];
}
#end

Actually, I figuered it out, all you have to do is make a class method in the AppController which stores the used object (self) as a class variable. Then you can access that object from anywhere. It might not be the most efficient way of doing it, I have no idea as I am just a beginner, but it worked for me! :D
Here's relevant the code:
-(void)awakeFromNib
{
[self setViewController:kStartScreenView];
viewObject = self;
}
+(id)getViewObject
{
return viewObject;
}
viewObject is defined as a class variable of type id.

Related

Modifying string content in NSTextView works under viewDidLoad method, but not under myMethod

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"];

Customized init method on an object to initWithWindowNibName an NSWindowController property

What I'm trying to do is create a method to an object which opens a window.
In this window I want to output some properties of the object's instance.
To do this I created a "Profile" subclass of NSObject, which has an NSWindowController property called "view".
#interface Profile : NSObject {
\\...
}
#property (readwrite, assign) NSWindowController *view;
Since I cannot connect "view" to the window with Interface Builder (or at least I don't know how) I have to do so with the "initWithWindowNibName". So I tried overriding the "Profile"'s init method like this:
-(Profile *)init{
self = [super init];
if(self){
[[self view] initWithWindowNibName:#"Profile"];
}
return self;
}
I don't know whether my approach is correct, fact is when I try showing the window it doesn't appear. Here's how I tried:
Profile *profile = [[Profile alloc] init];
[[profile view] showWindow:self];
Hope you can help :)
Don't you want something like:
#interface Profile:NSObject
#property (nonatomic, strong) NSWindowController *windowController;
#end
and:
- (Profile *)init {
self = [super init];
if( !self ) { return nil; }
self.windowController = [[NSWindowController alloc] initWithWindowNibName:#"Profile"];
return self;
}
and:
// show window
Profile *profile = [[Profile alloc] init];
[[profile windowController] showWindow:self];
(I'm assuming ARC.)
EDIT:
For clarity to the OP, I followed the his property nomenclature, which was to name the NSWindowController property view. It is confusing, though because a NSWindowController is not a view. For clarity to others, I've changed it.

Add and control UIImageView Subview from another class

I have two classes: MainViewController and PlayerImageController (NSObject)
How would I be able to add the subview of my UIImageView from PlayerImageController to my MainViewController and dictate actions like
- (void)viewDidLoad
{
[super viewDidLoad];
[self.view addSubview:[PlayerImageController addPlayerImage]];
}
- (void)somethingHappened
{
[PlayerImageController changePlayerImage];
}
and have my methods in my PlayerImageController class like
+ (UIImageView *) addPlayerImage
{
heroPlayerImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"hero-still.png"]];
[heroPlayerImageView setFrame:CGRectMake(151, 200, 17, 23)];
return heroPlayerImageView;
}
+ (void) changePlayerImage
{
//change image
}
Thanks!
I think in your case you should use the Delegate pattern.
Declare:
#protocol PlayerImageUpdater
- createPlayerImage;
- changePlayerImage;
#end
Then add:
#interface PlayerImageController <PlayerImageUpdater>
then add to MainViewController ivar and property like:
#property (...) id<PlayerImageUpdater> playerDelegate;
set this delegate like: mainViewController.playerDelegate = playerImageControllerInstance;
and use in code:
[playerDelegate createPlayerImage];
[playerDelegate changePlayerImage];
On one hand, I would not recommend using class methods but instance methods. This way, you could implement as many instances of your class as you need and keep a reference to your instances to update them.
On the other hand, if the UIImageView is the important attribute of your class, I suggest you implement it as a UIView subclass (if it is not, you can do it as an NSObject subclass as well, and get its UIImageView attribute).
Have a look at the following code:
PlayerImageController.h:
#interface PlayerImageController : UIView{
UIImageView *_heroPlayerImageView;
}
-(void) changePlayerImage;
PlayerImageController.m:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
_heroPlayerImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"hero-still.png"]];
// x = 0 and y = 0 because its relative to its parent view, the object itself.
[_heroPlayerImageView setFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)];
[self addSubview:_heroPlayerImageView];
}
return self;
}
MainViewController.h:
#import "PlayerImageController.h"
#interface MainViewController : UIViewController{
PlayerImageController *_player;
}
MainViewController.m:
- (void)viewDidLoad
{
[super viewDidLoad];
_player = [[PlayerImageController alloc] initWithFrame:CGRect(151, 200, 17, 23)];
[self.view addSubview:_player];
}
- (void)somethingHappened
{
[_player changePlayerImage];
}
I hope it can help you (I haven't actually tried the code above, it could have some syntax errors).
If you are not using ARC, remember to retain and release your variables! Good luck!

Pass a delegate method multi-levels up a navigationController stack

I have a button in a toolbar that has a popover associated with it. Within the popover, I've set up a navigationcontroller. What I'm trying to achieve is for a view controller two or three levels down in the navigationcontroller stack to change the state of the button that originally called the popover. I've managed to do this, but it requires a couple of delegates and seems very clunky; my reason for posting here is to figure out if there is a more elegant and efficient solution.
So, to get started:
//ProtocolDeclaration.h
#protocol ADelegate <NSObject>
- (void)changeButtonState;
#end
#protocol BDelegate <NSObject>
- (void)passTheBuckUpTheNavChain;
#end
Then, for my MainController that holds the button:
// MainController.h
#import "A_TableController.h"
#import "ProtocolDeclaration.h"
#class A_TableController;
#interface MainController : UIViewController <ADelegate>
...
#end
// MainController.m
- (IBAction)buttonPressed:(id)sender {
A_Controller *ac = [[[A_Controller alloc] init] autorelease];
ac.ADelegate = self;
UINavigationController *nc = [[[UINavigationController alloc] initWithRootViewController:ac] autorelease];
UIPopoverController *pc = [[[UIPopoverController alloc] initWithContentViewController:nc] autorelease];
[pc presentPopoverFromBarButtonItem...]
}
// ADelegate Method in MainController.m
- (void)changeButtonState
{
self.button.style = ....
}
Now, for A_Controller, my rootViewController for my navController:
//A_Controller.h
#import "B_Controller.h"
#import "ProtocolDeclaration.h"
#class B_Controller;
#interface A_Controller : UITableViewController <BDelegate>
{
id<ADelegate> delegate;
...
}
#property (assign) id<ADelegate> delegate;
...
#end
//A_Controller.m
//In the method that pushes B_Controller onto the stack:
B_Controller *bc = [[[B_Controller alloc] init] autorelease];
bc.BDelegate = self;
[self.navigationController pushViewController:bc animated:YES];
//In the BDelegate Method in A_Controller:
- (void)passTheBuckUpTheNavChain
{
[ADelegate changeButtonState];
}
Lastly, in B_Controller:
//B_Controller.h
#import "ProtocolDeclaration.h"
#interface A_Controller : UITableViewController
{
id<BDelegate> delegate;
...
}
#property (assign) id<BDelegate> delegate;
...
#end
//B_Controller.m
//Where it's necessary to change the button state back up in MainController:
[BDelegate passTheBuckUpTheNavChain];
Now, this works, but it seems like a sort of Rube-Goldberg-ish way of doing it. I tried init'ing both A_Controller and B_Controller in MainController and setting B_Controller's delegate to MainController right there, and then using a NSArray of the two viewcontrollers to set the navcontroller stack, but it really messed up the way the viewcontrollers appeared in the navcontroller: I'd get a back button even on the rootviewcontroller of the navcontroller and you could just keep clicking Back and going round and round the navcontroller stack instead of stopping at the root. Any ideas on a better way to do this?
If you want to decouple the view controllers you could define a notification, and just post that one.
This way only the root view controller that receives the notification needs to know about the deep nested view controller.
Define the notification like this:
// In .h
extern NSString* const BlaControllerDidUpdateNotification;
// In .m
NSString* const BlaControllerDidUpdateNotification = #"BlaControllerDidUpdateNotification";
The deeply nested controller (BlaController) need to post the message like this:
[[NSNotificationCenter defaultCenter]
postNotificationName:BlaControllerDidUpdateNotification
object:self];
And the root view controller need to act on it with something like this:
// In init or the like:
[[NSNotificationCenter defaultCender]
addObserver:self
selector:#selector(blaControllerDidUpdateNotification:)
name:BlaControllerDidUpdateNotification
object:nil];
// And then define this method:
-(void)blaControllerDidUpdateNotification:(NSNotification*)notification {
// Update UI or whatever here.
}

can't get form access from my class

#import <Foundation/Foundation.h>
#interface MyClass : NSObject {
#private
IBOutlet NSTextField *tf;
}
- (void)setStr;
#end
===========================================
#import "MyClass.h"
#implementation MyClass
- (id)init
{
self = [super init];
if (self) {
// Initialization code here.
}
return self;
}
- (void)dealloc
{
[super dealloc];
}
- (void)setStr
{
[tf setStringValue:#"aaaaaaaaa"];
}
#end
Call a method from my AppDelegate class
- (IBAction)test1:(id)sender
{
MyClass *m = [[MyClass alloc] init];
[m setStr];
}
I created an object of MyClass in .xib file. I correlated outlet of textfield with a textfield on form.
And there are no actions when button pressed.
where I'm wrong?
You need to create an outlet for MyClass in AppDelegate and connect it up, then use that outlet in test1 instead of creating a new instance. This guide from Apple should help you.
Another possibility is to put the test1 action into MyClass and have it called directly with the button press, or to make setStr an IBAction. These are possible, since you have a MyClass instance in your XIB.