I use storyboard in a OS X cocoa application project with a SplitView controller and 2 others view controller LeftViewController and RightViewController.
In the LeftViewController i have a tableView that display an array of name. The datasource and delegate of the tableview is the LeftViewController.
In the RightViewController i just have a centered label that display the select name. I want to display in the right view the name selected in the left view.
To configure the communication between the 2 views controllers i use the AppDelegate and i define 2 property for each controller in AppDelegate.h
The 2 property are initialized in the viewDidLoad of view controller using the NSInvocation bellow :
#implementation RightViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
id delg = [[NSApplication sharedApplication] delegate];
SEL sel1 = NSSelectorFromString(#"setRightViewController:");
NSMethodSignature * mySignature1 = [delg methodSignatureForSelector:sel1];
NSInvocation * myInvocation1 = [NSInvocation
invocationWithMethodSignature:mySignature1];
id me = self;
[myInvocation1 setTarget:delg];
[myInvocation1 setSelector:sel1];
[myInvocation1 setArgument:&me atIndex:2];
[myInvocation1 invoke];
}
I have the same in LeftViewController.
Then if i click on a name in the table view, i send a message to the delegate with the name in parameter and the delegate update the label of the RightViewController with the given name. It works fine but according to apple best practice it’s not good.
Is there another way to communicate between 2 view controller inside a storyboard ?
I've already read a lot of post but found nothing for OS X.
You can download the simple project here : http://we.tl/4rAl9HHIf1
This is more advanced topic of app architecture (how to pass data).
Dirty quick solution: post NSNotification together with forgotten representedObject:
All NSViewControllers have a nice property of type id called representedObject. This is one of the ways how to pass data onto NSViewController. Bind your label to this property. For this simple example we will set representedObject some NSString instance. You can use complex object structure as well. Someone can explain in comments why storyboards stopped to show representedObject (Type safety in swift?)
Next we add notification observer and set represented object in handler.
#implementation RightViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserverForName:#"SelectionDidChange" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
//[note object] contains our NSString instance
[self setRepresentedObject:[note object]];
}];
}
#end
Left view controller and its table:
Once selection changes we post a notification with our string.
#interface RightViewController () <NSTableViewDelegate, NSTableViewDataSource>
#end
#implementation RightViewController
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
{
return [[self names] count];
}
- (nullable id)tableView:(NSTableView *)tableView objectValueForTableColumn:(nullable NSTableColumn *)tableColumn row:(NSInteger)row
{
return [self names][row];
}
- (NSArray<NSString *>*)names
{
return #[#"Cony", #"Brown", #"James", #"Mark", #"Kris"];
}
- (void)tableViewSelectionDidChange:(NSNotification *)notification
{
NSTableView *tableView = [notification object];
NSInteger selectedRow = [tableView selectedRow];
if (selectedRow >= 0) {
NSString *name = [self names][selectedRow];
if (name) {
[[NSNotificationCenter defaultCenter] postNotificationName:#"SelectionDidChange" object:name];
}
}
}
PS: don't forget to hook tableview datasource and delegate in storyboard
Why is this solution dirty? Because once your app grows you will end up in notification hell. Also view controller as data owner? I prefer window controller/appdelegate to be Model owner.
Result:
AppDelegate as Model owner.
Our left view controller will get it's data from AppDelegate. It is important that AppDelegate controls the data flow and sets the data (not the view controller asking AppDelegate it's table content cause you will end up in data synchronization mess). We can do this again using representedObject. Once it's set we reload our table (there are more advanced solutions like NSArrayController and bindings). Don't forget to hook tableView in storyboard. We also modify tableview's delegate methos the tableViewSelectionDidChange to modify our model object (AppDelegate.selectedName)
#import "LeftViewController.h"
#import "AppDelegate.h"
#interface LeftViewController () <NSTableViewDelegate, NSTableViewDataSource>
#property (weak) IBOutlet NSTableView *tableView;
#end
#implementation LeftViewController
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView
{
return [[self representedObject] count];
}
- (nullable id)tableView:(NSTableView *)tableView objectValueForTableColumn:(nullable NSTableColumn *)tableColumn row:(NSInteger)row
{
return [self representedObject][row];
}
- (void)setRepresentedObject:(id)representedObject
{
[super setRepresentedObject:representedObject];
//we need to reload table contents once
[[self tableView] reloadData];
}
- (void)tableViewSelectionDidChange:(NSNotification *)notification
{
NSTableView *tableView = [notification object];
NSInteger selectedRow = [tableView selectedRow];
if (selectedRow >= 0) {
NSString *name = [self representedObject][selectedRow];
[(AppDelegate *)[NSApp delegate] setSelectedName:name];
} else {
[(AppDelegate *)[NSApp delegate] setSelectedName:nil];
}
}
In RightViewController we delete all code. Why? Cause we will use binding AppDelegate.selectedName <--> RightViewController.representedObject
#implementation RightViewController
#end
Finally AppDelegate. It needs to expose some properties. What is interesting is how do I get my hands on all my controllers? One way (best) is to instantiate our own window controller and remember it as property. The other way is to ask NSApp for it's windows (be careful here with multiwindow app). From there we just ask contentViewController and loop through childViewControllers. Once we have our controllers we just set/bind represented objects.
#interface AppDelegate : NSObject <NSApplicationDelegate>
#property (nonatomic) NSString *selectedName;
#property (nonatomic) NSMutableArray <NSString *>*names;
#end
#import "AppDelegate.h"
#import "RightViewController.h"
#import "LeftViewController.h"
#interface AppDelegate () {
}
#property (weak, nonatomic) RightViewController *rightSplitViewController;
#property (weak, nonatomic) LeftViewController *leftSplitViewController;
#property (strong, nonatomic) NSWindowController *windowController;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
_names = [#[#"Cony", #"Brown", #"James", #"Mark", #"Kris"] mutableCopy];
_selectedName = nil;
NSStoryboard *storyboard = [NSStoryboard storyboardWithName:#"Main"
bundle:[NSBundle mainBundle]];
NSWindowController *windowController = [storyboard instantiateControllerWithIdentifier:#"windowWC"];
[self setWindowController:windowController];
[[self windowController] showWindow:nil];
[[self leftSplitViewController] setRepresentedObject:[self names]];
[[self rightSplitViewController] bind:#"representedObject" toObject:self withKeyPath:#"selectedName" options:nil];
}
- (RightViewController *)rightSplitViewController
{
if (!_rightSplitViewController) {
NSArray<NSViewController *>*vcs = [[[self window] contentViewController] childViewControllers];
for (NSViewController *vc in vcs) {
if ([vc isKindOfClass:[RightViewController class]]) {
_rightSplitViewController = (RightViewController *)vc;
break;
}
}
}
return _rightSplitViewController;
}
- (LeftViewController *)leftSplitViewController
{
if (!_leftSplitViewController) {
NSArray<NSViewController *>*vcs = [[[self window] contentViewController] childViewControllers];
for (NSViewController *vc in vcs) {
if ([vc isKindOfClass:[LeftViewController class]]) {
_leftSplitViewController = (LeftViewController *)vc;
break;
}
}
}
return _leftSplitViewController;
}
- (NSWindow *)window
{
return [[self windowController] window];
}
//VALID SOLUTION IF YOU DON'T INSTANTIATE STORYBOARD
//- (NSWindow *)window
//{
// return [[NSApp windows] firstObject];
//}
#end
Result: works exactly the same
PS: If you instantiate own window Controller don't forget to delete initial controller from Storyboard
Why is this better? Cause all changes goes to model and models sends triggers to redraw views. Also you will end up in smaller view controllers.
What can be done more? NSObjectController is the best glue between your model objects and views. It also prevents retain cycle that sometimes can happen with bindings (more advanced topic). NSArrayController and so on...
Caveats: not a solution for XIBs
I managed to get what i want by adding the following code in AppDelegate.m :
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
//
NSStoryboard *storyboard = [NSStoryboard storyboardWithName:#"Main"
bundle:[NSBundle mainBundle]];
self.windowController = [storyboard instantiateControllerWithIdentifier:#"windowController"];
self.window = self.windowController.window;
self.splitViewController = (NSSplitViewController*)self.windowController.contentViewController;
NSSplitViewItem *item0 = [self.splitViewController.splitViewItems objectAtIndex:0];
NSSplitViewItem *item1 = [self.splitViewController.splitViewItems objectAtIndex:1];
self.leftViewController = (OMNLeftViewController*)item0.viewController;
self.rightViewController = (OMNRightViewController*)item1.viewController;
[self.window makeKeyAndOrderFront:self];
[self.windowController showWindow:nil];
}
We also need to edit the storyboard NSWindowController object as follow :
Uncheck the checkbox 'Is initial controller' because we add it programmatically in AppDelegate.m.
Now the left and right view can communicate. Just define a property named rightView in OMNLeftViewController.h :
self.leftViewController.rightView = self.rightViewController;
Related
I am writing a very simple macOS application that I'd like to show a few images in a collection view. I don't need any special behavior for how they are displayed. The docs for NSCollectionViewItem say:
The default implementation of this class supports the creation of a simple item that displays a single image or string.
That is what I want. However, I can't find any information on how to create a default NSCollectionViewItem.
The documentation for NSCollectionView states:
Every data source object is required to implement the following methods:
collectionView:numberOfItemsInSection:
collectionView:itemForRepresentedObjectAtIndexPath:
The second method above returns an NSCollectionViewItem. From reading examples I gather that the traditional way of creating an NSCollectionViewItem in this case is to call:
NSCollectionViewItem* newCollectionViewItem = [imageCollectionView makeItemWithIdentifier:<some identifier>
forIndexPath:indexPath];
The problem is that I don't understand what <some identifier> should be. I don't have a nib that contains an NSCollectionViewItem because I'm not subclassing it or customizing it in any way. I've tried adding the following to my data source:
- (void)awakeFromNib
{
[imageCollectionView registerClass:[NSCollectionViewItem class]
forItemWithIdentifier:#"Image"];
}
where imageCollectionView is the NSCollectionView in question. And then in my - (NSCollectionViewItem *)collectionView:itemForRepresentedObjectAtIndexPath: method, I call:
NSCollectionViewItem* newCollectionViewItem = [imageCollectionView makeItemWithIdentifier:#"Image"
forIndexPath:indexPath];
But this throws an exception and prints this to the console:
2016-12-19 17:51:27.463 MyApp[28177:3926764] -[NSNib _initWithNibNamed:bundle:options:] could not load the nibName: NSCollectionViewItem in bundle (null).
followed by a stack trace.
So how do I go about creating and using an NSCollectionViewItem that isn't subclassed or modified in any way?
Here is a very simple example which uses a Nib for the item's prototype:
#interface ViewController()
#property (weak) IBOutlet NSCollectionView *collectionView;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSNib *theNib = [[NSNib alloc] initWithNibNamed:#"Item" bundle:nil];
[self.collectionView registerNib:theNib forItemWithIdentifier:#"item"];
}
#pragma mark NSCollectionViewDataSource
- (NSInteger)numberOfSectionsInCollectionView:(NSCollectionView *)inCollectionView {
return 1;
}
- (NSInteger)collectionView:(NSCollectionView *)inCollectionView numberOfItemsInSection:(NSInteger)inSection {
return 10;
}
- (NSCollectionViewItem *)collectionView:(NSCollectionView *)inCollectionView itemForRepresentedObjectAtIndexPath:(NSIndexPath *)inIndexPath {
NSCollectionViewItem *theItem = [inCollectionView makeItemWithIdentifier:#"item" forIndexPath:inIndexPath];
NSTextField *theLabel = (NSTextField *)theItem.view;
theLabel.stringValue = [NSString stringWithFormat:#"%d.%d", (int)inIndexPath.section, (int)inIndexPath.item];
return theItem;
}
#end
The NIB contains just a NSCollectionViewItem with a text field as view.
Addendum:
I think you should create a NSCollectionViewItem.xib for the registerClass variant. A view controller will search for a NIB with its class name, if you doesn't create its view manually in loadView. Thus, you can't use plain NSCollectionViewItem without a NIB for registering a class, because of makeItemWithIdentifier:forIndexPath: will access the view of the item.
I found a way to set up NSCollectionView without needing to add a .xib file.
The trick was inheriting from NSCollectionViewItem and using that subclass for -[NSCollectionView registerClass:forItemWithIdentifier:].
#import "ViewController.h"
#interface MyViewItem : NSCollectionViewItem
#end
#implementation MyViewItem {
}
- (instancetype)initWithNibName:(NSNibName)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nil bundle:nil];
NSButton *button = [[NSButton alloc] initWithFrame:NSZeroRect];
[button setTitle:#"Button"];
[self setView:button];
return self;
}
#end
#implementation ViewController {
IBOutlet NSCollectionView *_collectionView;
}
- (void)viewDidLoad {
[super viewDidLoad];
[_collectionView registerClass:[MyViewItem class]
forItemWithIdentifier:#"item"];
[_collectionView setDataSource:self];
}
- (NSInteger)collectionView:(NSCollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return 10;
}
- (NSCollectionViewItem *)collectionView:(NSCollectionView *)collectionView itemForRepresentedObjectAtIndexPath:(NSIndexPath *)indexPath {
return [_collectionView makeItemWithIdentifier:#"item" forIndexPath:indexPath];
}
#end
Anybody know how to implement search template like in Apple tvOS Human Interface Guidelines, using native development in Objective-C or Swift, without TVML ?
So, after research I was able to find a solution:
Objective - C
If in application is tabBar, i created a subclass from UITabBarController e.g. APTabBarController. In APTabBarController, in method
- (void)viewDidLoad
I do next:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
SearchResultsViewController *myViewController = [storyboard instantiateViewControllerWithIdentifier:#"SearchResultsViewController"];
UISearchController *searchController = [[UISearchController alloc] initWithViewController:myViewController];
UISearchContainerViewController *containerVC = [[UISearchContainerViewController alloc] initWithSearchController: searchController];
containerVC.title = #"Search";
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController: containerVC];
NSMutableArray *newTab = [self.viewControllers mutableCopy];
[newTab addObject: navigationController];
[self setViewControllers: newTab];
Where:
storyboard - is my storyboard
SearchResultsViewController - is my controller from storyboard that contains collectionView
UISearchController - is controller that allow to find what do you need
UISearchContainerViewController - and these one is like a view controller from tabBarController
In "newTab" - I add fresh created viewController that i need
But, problem that I found is that i can't catch searched text. For that, create a subclass from UISearchController, and implement custom
initWithViewController
In my case it looks like these:
In .h
#import <UIKit/UIKit.h>
#interface SearchExercisesViewController : UISearchController
- (id) initWithViewController:(UIViewController *) viewController;
#end
In .m
#import "SearchExercisesViewController.h"
#interface SearchExercisesViewController () <UISearchBarDelegate>
#property (nonatomic, strong) UIViewController *viewController;
#end
#implementation SearchExercisesViewController
- (id) initWithViewController:(UIViewController *) viewController {
self = [super initWithSearchResultsController:viewController];
if (self) {
self.viewController = viewController;
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.searchBar.delegate = self;
}
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
NSLog(#"%#",searchText);
}
#end
Profit, and now, replace
UISearchController *searchController = [[UISearchController alloc] initWithViewController:myViewController];
with
SearchExercisesViewController *searchController = [[SearchExercisesViewController alloc] initWithViewController:myViewController];
All done. Now only that remain is to sent data to viewController that contains collection view, and implement logic for search. For sent data you can you Delegate pattern or NSNotification. You can find how to implement that in that post:
it possible to Pass Data with popViewControllerAnimated?
Swift
In swift is the same, how to do that, you can find on Apple example from these link:
https://github.com/brunogb/TVExamples/tree/master/UIKitCatalogtvOSCreatingandCustomizingUIKitControls
Sounds like you want to look at UISearchController.
How do I get a reference to the UIViewController of a touched view?
I am using a UIPanGestureRecognizer on the view of a UIViewController. Here's how I initialize it:
TaskUIViewController *thisTaskController = [[TaskUIViewController alloc]init];
[[self view]addSubview:[thisTaskController view]];
UIPanGestureRecognizer *panRec = [[UIPanGestureRecognizer alloc]initWithTarget:self action:#selector(handlePan:)];
[[thisTaskController view] addGestureRecognizer:panRec];
In the tiggered action triggered using the gesture recognizer I am able to get the view from the parameter using recognizer.view
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
UIView *touchedView = [[UIView alloc]init];
touchedView = (UIView*)[recognizer view];
...
}
However what I really need is the underlying UIViewController of the view touched. How can I get a reference to the UIViewController that contains this view instead of only the UIView?
I would say that it is more a design issue than just getting a reference. So I would follow several simple advises:
Owner should catch events from its view. I.e. TaskUIViewController sould be a target to UIPanGestureRecognizer which you added to its view.
If a controller has a sub-controller and waits from its sub-controller some responses - implement this as delegate.
You have memory leak in your "handlePan:" method.
Here is a skeleton to solve your issue:
#protocol CallbackFromMySubcontroller <NSObject>
- (void)calbackFromTaskUIViewControllerOnPanGesture:(UIViewController*)fromController;
#end
#interface OwnerController : UIViewController <CallbackFromMySubcontroller>
#end
#implementation OwnerController
- (id)init
{
...
TaskUIViewController *thisTaskController = [[TaskUIViewController alloc] init];
...
}
- (void)viewDidLoad
{
...
[self.view addSubview:thisTaskController.view];
...
}
- (void)calbackFromTaskUIViewControllerOnPanGesture:(UIViewController*)fromController
{
NSLog(#"Yahoo. I got an event from my subController's view");
}
#end
#interface TaskUIViewController : UIViewController {
id <CallbackFromMySubcontroller> delegate;
}
#end
#implementation TaskUIViewController
- (id)initWithOwner:(id<CallbackFromMySubcontroller>)owner
{
...
delegate = owner;
...
}
- (void)viewDidLoad
{
UIPanGestureRecognizer *panRec = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
[self.view addGestureRecognizer:panRec];
[panRec release];
}
- (void)handlePan:(UIPanGestureRecognizer *)recognizer {
...
[delegate calbackFromTaskUIViewControllerOnPanGesture:self];
...
}
#end
[touchedView nextResponder] will return the UIViewController object that manages touchedView (if it has one) or touchedView's superview (if it doesn’t have a UIViewController object that manages it).
For more information, see the UIResponder Class Reference. (UIViewController and UIView are subclasses of UIResponder.)
In your case, since you happen to know that touchedView is your viewController's view (and not, for instance, a subview of your viewController's view), you can just use:
TaskUIViewController *touchedController = (TaskUIViewController *)[touchedView nextResponder];
In the more general case, you could work up the responder chain until you find an object of kind UIViewController:
id aNextResponder = [touchedView nextResponder];
while (aNextResponder != nil)
{
if ([aNextResponder isKindOfClass:[UIViewController class]])
{
// we have found the viewController that manages touchedView,
// so we break out of the while loop:
break;
}
else
{
// we have yet to find the managing viewController,
// so we examine the next responder in the responder chain
aNextResponder = [aNextResponder nextResponder];
}
}
// outside the while loop. at this point aNextResponder points to
// touchedView's managing viewController (or nil if it doesn't have one).
UIViewController *eureka = (UIViewController *)aNextResponder;
I'm quite new to Cocoa and I am trying to setup a table view backed by an array. I've setup the app delegate as the datasource for the tableview, and implemented NSTableViewDataSource protocol.
When I run the app, I get the following log output:
2012-06-23 18:25:17.312 HelloWorldDesktop[315:903] to do list is nil
2012-06-23 18:25:17.314 HelloWorldDesktop[315:903] Number of rows is 0
2012-06-23 18:25:17.427 HelloWorldDesktop[315:903] App did finish
launching
I thought that when I called reloadData on the tableView it would call numberOfRowsInTableView:(NSTableView *)tableView again to refresh the view, but that doesn't seem to be happening. What have I missed?
My .h and .m listings are below.
AppDelegate.h
#import <Cocoa/Cocoa.h>
#interface AppDelegate : NSObject <NSApplicationDelegate, NSTableViewDataSource>
#property (assign) IBOutlet NSWindow *window;
#property (assign) IBOutlet NSTableView * toDoListTableView;
#property (assign) NSArray * toDoList;
#end
AppDelegate.m
#import "AppDelegate.h"
#implementation AppDelegate
#synthesize window = _window;
#synthesize toDoList;
#synthesize toDoListTableView;
- (void)dealloc
{
[self.toDoList dealloc];
[super dealloc];
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSLog(#"App did finish launching");
// Insert code here to initialize your application
// toDoList = [[NSMutableArray alloc] init];
toDoList = [[NSMutableArray alloc] initWithObjects:#"item 1", #"item 2", nil];
[self.toDoListTableView reloadData];
// NSLog(#"table view %#", self.toDoListTableView);
}
//check toDoList initialised before we try and return the size
- (NSInteger) numberOfRowsInTableView:(NSTableView *)tableView {
NSInteger count = 0;
if(self.toDoList){
count = [toDoList count];
} else{
NSLog(#"to do list is nil");
}
NSLog(#"Number of rows is %ld", count);
return count;
}
-(id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
NSLog(#"in objectValueForTable");
id returnVal = nil;
NSString * colId = [tableColumn identifier];
NSString * item = [self.toDoList objectAtIndex:row];
if([colId isEqualToString:#"toDoCol"]){
returnVal = item;
}
return returnVal;
}
#end
The first thing that I'd check is that you're NSTableView IBOutlet is still set in applicationDidFinishLaunching.
NSLog(#"self.toDoListTableView: %#", self.toDoListTableView)
You should see output like:
<NSTableView: 0x178941a60>
if the outlet is set properly.
If you see 'nil' rather than an object, double check to ensure that your NSTableView is connected to your outlet in the XIB editing mode of Xcode. Here's a documentation link for assistance connecting outlets.
I fixed it - I'd set the appDelegate as the datasource and the delegate for the tableView but ctrl-dragging from the tableView to the appDelegate, but I hadn't ctrl-dragged the other way to actually link up the outlet I'd declared with the table view. It's working now. Thanks for your help though Jeff.
I know there are plenty of other questions addressing the same problem, but since I'm using a custom keyboard, I thought my problem would be slightly different.
This is the specific error:
-[EquationTextField element1Pressed:]: unrecognized selector sent to instance 0x4b68ee0
2012-01-02 12:23:44.630 rowQuiz[20975:207] Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[EquationTextField element1Pressed:]: unrecognized selector sent to instance 0x4b68ee0'
I have a view controller, quizController. Inside quizController is a custom view, textField (added through interface builder).
When textField is tapped, another custom view, formulaKeyboard, pops up as its keyboard. When a button on the keyboard is pressed, method element1Pressed: is called, and the error described above appears.
Some other questions say that there must be a problem with the retain count, so I tried retaining and releasing quizController in the app delegate, which didn't solve the problem.
It is also possible that I hooked up something incorrectly in Interface Builder; For the custom keyboard, File's owner and the main view are set to class elementKeyboard. For quizController, File's owner is set to quizController and hooked up to it's view.
Below is the code of the textField's class.
EquationTextField.h
#import <UIKit/UIKit.h>
#import "FormulaKeyboard.h"
#interface EquationTextField : UIView <KeyInput> {
FormulaKeyboard *keyboard;
NSString *lastElement;
}
#property (readwrite, retain) UIView *inputView;
#end
EquationTextField.m
- (id)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
self.userInteractionEnabled = YES;
[self addGestureRecognizer:
[[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(becomeFirstResponder)]];
NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:#"FormulaKeyboard" owner:self options:nil];
for (id object in bundle) {
if ([object isKindOfClass:[FormulaKeyboard class]])
keyboard = (FormulaKeyboard *)object;
}
self.inputView = keyboard;
keyboard.delegate = self;
}
return self;
}
- (BOOL)canBecomeFirstResponder {
return YES;
}
#pragma mark -- KeyInput Protocol Methods
- (void)addElement:(NSString *)elementName {
}
- (void)addCharge:(NSString *)chargeIncrease {
}
- (void) addState:(NSString *)stateName {
}
- (void)deleteCharacter {
}
- (void)dealloc
{
[super dealloc];
}
formulaKeyboard.h
#import <UIKit/UIKit.h>
#protocol KeyInput <UITextInputTraits>
- (void) addElement:(NSString*) elementName;
- (void) addCharge:(NSString*) chargeIncrease;
- (void) addState:(NSString*) stateName;
- (void) deleteCharacter;
#end
#interface FormulaKeyboard : UIView {
id <KeyInput> delegate;
}
#property (nonatomic, retain) id <KeyInput> delegate;
-(IBAction) element1Pressed:(id)sender;
-(IBAction) element2Pressed:(id)sender;
-(IBAction) element3Pressed:(id)sender;
-(IBAction) element4Pressed:(id)sender;
-(IBAction) element5Pressed:(id)sender;
-(IBAction) element6Pressed:(id)sender;
-(IBAction) chargePlusPressed:(id)sender;
-(IBAction) chargeMinusPressed:(id)sender;
-(IBAction) solidSatePressed:(id)sender;
-(IBAction) liquidStatePressed:(id)sender;
-(IBAction) gasStatePressed:(id)sender;
#end
formulaKeyboard.m
- (IBAction)element1Pressed:(id)sender {
[delegate addElement:#"Na"];
}
- (void)element2Pressed:(id)sender {
[delegate addElement:#"N"];
}
- (void)element3Pressed:(id)sender {
[delegate addElement:#"O"];
}
- (void)element4Pressed:(id)sender {
}
- (void)element5Pressed:(id)sender {
}
- (void)element6Pressed:(id)sender {
}
appDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
quizController = [[QuizController alloc] initWithNibName:#"QuizController" bundle:nil];
[self.window addSubview:quizController.view];
[self.window makeKeyAndVisible];
return YES;
}
- (void)dealloc
{
[_window release];
[quizController release];
[super dealloc];
}
The action of the keyboard's buttons are pointing to the wrong place. You've probably got them wired to File's Owner inside the FormulaKeyboard nib when they should be wired to the FormulaKeyboard object you're creating inside the nib.
NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:#"FormulaKeyboard" owner:self options:nil];
That's being called from EquationTextField, so self would be your instance of EquationTextField. If you're keyboard's targets are pointing there, that's why you get the unrecognized selector exception.
What's happening here is a method called element1Pressed: is being sent to an instance of EquationTextField. You need to actually add the method to the class for it to work. Right now, it's sending the message to the field class, but there's no matching method, so it's throwing an error.
Also, I can't be completely sure about this, since you haven't posted the whole code and/or NIB info, but it seems that you may be going about this the wrong way. You should be using a view controller to handle everything, rather than a custom text field class. I notice that you haven't posted any code for the QuizController class. Once you do so, I may be able to give you more advice.
EDIT: Now that you've posted more code, I think I see the problem. You want the FormulaKeyboard instance to receive the event, but the event is linked to the EquationTextField instance instead. Make sure you wire it to an instance of FormulaKeyboard instead.
On the other hand, it seems that you may not have an instance of FormulaKeyboard in the NIB at all. Add an NSLog after keyboard = (FormulaKeyboard *)object to test if keyboard is ever actually assigned a value. If the NSLog doesn't fire, double-check that you've actually added an instance of FormulaKeyboard to the NIB.