Custom Keyboard on iOS: How do I access the UITextField? - objective-c

I have a UIView subclass that I assign to a text field as follows:
self.textField.inputView = [[HexKeyboard alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
and this works (i.e., the keyboard comes up). However, how should the HexKeyboard instance know about the textField?
[Of course I can add a property to the HexKeyboard to achieve this (and call it delegate), but I figure there's a built-in mechanism for this...]

You don't really need a complex delegate pattern for this. Just create a property of type UITextField on your HexKeyboard class, and make it an unsafe_unretained reference so you don't get a retain loop:
#interface HexKeyboard
#property (nonatomic, unsafe_unretained) UITextField *textField;
#end
Then set it when you set your inputView:
self.textField.inputView = [[HexKeyboard alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
self.textField.inputView.textField = self.textField;

There seems to be no built-in mechanism for this, as the other answerers have pointed out. As Nick says, you don't need a complex delegate pattern for this. Or rather, you use the delegate pattern, but you get the delegate class for free. In this case it's the UITextInput protocol.
So your keyboard probably looks like this (and has a NIB)
#interface ViewController : UIViewController
// use assign if < iOS 5
#property (nonatomic, weak) IBOutlet id <UITextInput> *delegate;
#end
When you create the keyboard controller, you assign the UITextInput conformer to it, so something like this:
- (void)viewDidLoad
{
[super viewDidLoad];
HexKeyboardController *keyboardController = [[HexKeyboardController alloc] initWithNibName:#"HexKeyboardController" bundle:nil];
self.textField.inputView = keyboardController.view;
keyboardController.delegate = self.textField;
}
However, I thought, there MUST be a way to define this keyboard just once and get the keyboard to "automatically know" who the UITextInput object that summoned it is. But I've looked around to no avail... you cannot figure out who the firstResponder is unless you troll the view hierarchy yourself or retain your delegates in a list (which would cause a retain loop). Plus, this isn't so bad because the HexKeyboardController will unload, too, when the textField is dealloced.

I dont believe there is a built in mechanism for this, you probably want the a delegate in the hex keyboard that will receive the "keystrokes" from it and then append it to the textfield, or whatever it is you need to do..

Related

Why is this delegate method automatically called in Objective-C?

I'm going through this book called "cocoa programming for mac os x" and I just started with delegates. This whole thing with delegates is still a little bit wacky to me but I think I just need to let it settle.
However there was this one exercise where I should implement a delegate of the main window so that if resized height is always 2xwidth.
So I got 4 files:
AppDelegate.h
AppDelegate.m
WindowDelegate.h
WindowDelegate.m
AppDelegate are just the two standard files that get created when you open a new Cocoa project. I had to look up the solution because I didn't quite know how to accomplish this task.
The solution was just to create a new cocoa class, "WindowDelegat.h/.m" and add this to it's implementation file:
- (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)frameSize {
NSSize newSize = frameSize;
newSize.height = newSize.width * 2;
return newSize;
}
Then I opened the interface builder, added a new object and made it my WindowDelegate. I then had to ctrl drag from the WindowDelegate to the actual window and made it the window's delegate.
Clicked run and it worked. Yay! But why?
First I thought that "windowWillResize" is just one of these callback functions that get's called as soon as the window is resized but it isn't. Normally methods get invoked because the general lifecycle of an program invokes them or because they are an #IBAction, a button or different control elements.
But "windowWillResize" is non of them. So why is it called?
EDIT: Problem solved! Thanks a lot!
Now I'm trying to connect the delegate to the window programmatically. Therefore I deleted the referencing outlet from WindowDelegate to the actual window in interface builder. It works but I just want to verify that this it the correct way how it's done:
AppDelegate.h
#import <Cocoa/Cocoa.h>
#import "WindowDelegate.h"
#interface AppDelegate : NSObject <NSApplicationDelegate>
#end
AppDelegate.m
#import "AppDelegate.h"
#interface AppDelegate ()
#property (weak) IBOutlet NSWindow *window;
#property (strong) WindowDelegate *winDeleg;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
- (void)awakeFromNib {
[_window setOpaque:NO];
NSColor *transparentColor = [NSColor colorWithDeviceRed:0.0 green:0.0 blue:0.0 alpha:0.5];
[_window setBackgroundColor:transparentColor];
NSSize initialSize = NSMakeSize(100, 200);
[_window setContentSize:initialSize];
_winDeleg = [[WindowDelegate alloc] init];
[_window setDelegate: _winDeleg];
}
#end
WindowDelegate.h
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
#interface WindowDelegate : NSObject <NSWindowDelegate>
#end
WindowDelegate.m
#import "WindowDelegate.h"
#implementation WindowDelegate
- (NSSize)windowWillResize:(NSWindow *)sender toSize:(NSSize)frameSize {
NSSize newSize = frameSize;
newSize.height = newSize.width * 2;
return newSize;
}
- (id)init {
self = [super init];
return self;
}
#end
Why does the #property of WindowDelegate need to be strong?
And isn't my winDeleg an object? Why do I have to access it through _winDeleg when it's an object. I though the underscore is used to access variables?
Thank you for your help!
Clicked run and it worked. Yay! But why?
Because instances of NSWindow have a delegate property that can point to any object that implements the NSWindowDelegate protocol, and that protocol includes the -windowWillResize:toSize: method.
Read that a few times. The reason it's important is that you can create your own object, say that it implements NSWindowDelegate, implement -windowWillResize:toSize:, and set that object as your window's delegate. Then, whenever the user resizes the window, your method will be called and can modify the proposed new size.
Normally methods get invoked because the general lifecycle of an program invokes them or because they are an #IBAction, a button or different control elements. But "windowWillResize" is non of them. So why is it called?
This really isn't so different. Think of delegates as "helper objects." They let you customize the behavior of an object without having to create a whole new subclass. The NSWindowDelegate object is essentially a contract that the NSWindow promises to follow: whenever certain things happen, such as the user resizing the window, the window will call certain methods in its delegate object, if the delegate exists and implements those methods. In the case of NSApplication, a lot of those delegate methods are application lifecycle events, like the app starting up or quitting or getting a message from the operating system. In the case of NSWindow, delegate methods correspond to interesting events that can happen to a window, like the user moving it, hiding it, showing it, maximizing it, moving it to a different screen, etc. Other classes, like text views or network connections or movie players, have their own sets of interesting events and their own delegate protocols to match.
Note that methods marked IBAction really aren't delegate methods, they're just methods that get called by objects like controls that use a target/action paradigm. The IBAction keyword lets the IDE know which methods it should present as possible actions for things like buttons. You often find actions in window controllers and view controllers, and those objects frequently act as a delegate for some other object, but the actions themselves aren't part of the delegate protocol. For example, NSTableView takes a delegate object that determines how the table will act and what's displayed in it. It often makes sense for the view controller that manages the table to be the table's delegate, and that same view controller might also manage some buttons and contain the action methods that said buttons trigger, but the actions aren't part of the NSTableViewDelegate protocol and you therefore wouldn't call them delegate methods.

Programmatically create NSPopupButton and add items to list

I've been able to programmatically create a NSPopupButton and add it to my window, and I can add items to the list from the same method, but I'd like to figure out how I can add items to it from another method.
Here's what I have so far that works:
// in my .h file:
#interface AVRecorderDocument : NSDocument
{
#private
NSPopUpButton *button;
}
#property (assign) IBOutlet NSWindow *mainWindow;
// in my .m file:
#implementation AVRecorderDocument
#synthesize mainWindow;
- (void)windowControllerDidLoadNib:(NSWindowController *) aController
{
NSView *superview = [mainWindow contentView];
NSRect frame = NSMakeRect(10,10,149,22);
NSPopUpButton *button = [[NSPopUpButton alloc] initWithFrame:frame];
[superview addSubview:button];
[button release];
}
- (void)refreshDevices
{
// I'd like to add items to my popupbutton here:
// [button addItemWithTitle: #"Item 1"];
}
#end
Up in refreshDevices I don't get a compiler error, just nothing gets added to the popupbutton. The method refreshDevices is called from -(id)init. I've also tried putting the code that is inside the windowControllerDidLoadNib at the top of my init section, but it won't even create the popupbutton there.
There are a two problems with your code:
Inside windowControllerDidLoadNib:
You don't assign the newly created button to your ivar but only to a function local variable (with the same name as your ivar).
Why nothing happens inside refreshDevices
init is called before windowControllerDidLoadNib:, so your ivar is nil (and because of 1.). Sending messages to nil does nothing.
Solution:
Remove NSPopUpButton * from windowControllerDidLoadNib: so you assign the new button to your ivar and not to some function local variable.
Call refreshDevices at the end of windowControllerDidLoadNib: or at some point you know windowControllerDidLoadNib: has been called and your button is not nil.
Edit:
You should keep in mind that the moment you remove the button from the superview it is probably deallocated because you release it after creation.
The moment it is deallocated your button ivar points to an invalid/deallocated object which leads to undefined behaviour when used in this state.
I'd advise to release the button inside dealloc so you can be sure to have a valid object throughout the whole lifetime of your document object.
But nonetheless I don't know your exact use case which might require this design.

How I can get the value of my UITextField?

how I can get the value of my UITextField ? When I declare my UITextField in the Storyboard, I know but like this, I don't know.
(sorry for my English, I'm French)
Thank you in advance for your answer.
//Ajout d'un Text Field
CGRect rectTF = CGRectMake(10,70,100,20); // Définition d'un rectangle
UITextField *articleSaisi = [[UITextField alloc] initWithFrame:rectTF];
articleSaisi.borderStyle = UITextBorderStyleLine;
articleSaisi.placeholder = #"Article";
[self.view addSubview: articleSaisi];
I am not quite sure whether you want to access the value or you want to associate your UI element to your code:
Try this if you are saying that you want to access the text value.
atricleSaisi.text
Try to control drag the UI element to either your corresponding .h or .m file so that it can create IBOutlet for you if you are saying that you want to connect your UI element to your code.
Highly recommend you go check the documentation.
if you want to know when the user pressed return, as per your comment, then you should create a UITextViewDelegate for your text view and define its textFieldShouldReturn method:
Discussion
The text field calls this method whenever the user taps the return button. You can use this method to implement any custom behavior when the button is tapped.
Also, give a look at textFieldDidEndEditing, which is called whenever there is a focus change and you should define to correctly handle user input.
Old answer:
If I do not understand you incorrectly, you want to create a UITextField programmatically (i.e., not through a Storyboard).
In this case, you should put you initialisation code inside the viewDidLoad method of your view controller and make sure that you define a property for the text field instead of a local variable:
#interface MyViewController : UIViewController
....
#property (nonatomic, strong) UITextField* articleSaisi;
...
#end
- (void)viewDidLoad {
CGRect rectTF = CGRectMake(10,70,100,20); // Définition d'un rectangle
self.articleSaisi = [[UITextField alloc] initWithFrame:rectTF];
self.articleSaisi.borderStyle = UITextBorderStyleLine;
self.articleSaisi.placeholder = #"Article";
[self.view addSubview:self.articleSaisi];
}
If you do so, you can access the text field value from any other method in the view controller through its text property:
self.articleSaisi.text

Subclassing UIButton but can't access my properties

I've created a sub class of UIButton:
//
// DetailButton.h
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
#interface MyDetailButton : UIButton {
NSObject *annotation;
}
#property (nonatomic, retain) NSObject *annotation;
#end
//
// DetailButton.m
//
#import "MyDetailButton.h"
#implementation MyDetailButton
#synthesize annotation;
#end
I figured that I can then create this object and set the annotation object by doing the following:
MyDetailButton* rightButton = [MyDetailButton buttonWithType:UIButtonTypeDetailDisclosure];
rightButton.annotation = localAnnotation;
localAnnotation is an NSObject but it is really an MKAnnotation. I can't see why this doesn't work but at runtime I get this error:
2010-05-27 10:37:29.214 DonorMapProto1[5241:207] *** -[UIButton annotation]: unrecognized selector sent to instance 0x445a190
2010-05-27 10:37:29.215 DonorMapProto1[5241:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[UIButton annotation]: unrecognized selector sent to instance 0x445a190'
'
I can't see why it's even looking at UIButton because I've subclassed that so it should be looking at the MyDetailButton class to set that annotation property. Have I missed something really obvious. It feels like it :)
Thanks in advance for any help you can provide
Ross
UIButton is a class cluster, which implies that Apple's implementation of buttonWithType: probably looks something like this:
+(id)buttonWithType:(UIButtonType)t {
switch (t) {
case UIButtonTypeDetailDisclosure:
return [[[PrivateDetailDisclosureButtonClass alloc] init] autorelease];
case ...
}
}
So when you call [MyDetailButton buttonWithType:UIButtonTypeDetailDisclosure]; you don't get an instance of MyDetailButton, you get an instance of PrivateDetailDisclosureButtonClass (or whatever Apple actually calls it).
Note, however, that you can get buttonWithType to instantiate a subclass if you call it with UIButtonTypeCustom (At least in the simulator running v3.0):
// LGButton is a straightforward subclass of UIButton
LGButton *testBtn = [LGButton buttonWithType:UIButtonTypeCustom];
LGButton *testBtn2 = [LGButton buttonWithType:UIButtonTypeDetailDisclosure];
NSLog(#"testBtn: %#, testBtn2: %#", [testBtn class], [testBtn2 class]);
// Output: testBtn: LGButton, testBtn2: UIButton
I made the same attempt as original poster but it seems that subclassing UIButton to do something like this is hard.
What I did, which is a hack - but works for me now, is add a UITextField as subview of the UIButton. The UITextField has no frame and is hidden, but I can freely store text strings in the textfield's text property. Which is what I wanted to do...
UITextField* tf = [[UITextField alloc] init];
tf.text = #"the text that I wanna store";
tf.hidden = YES;
tf.tag = TAGOFBUTTONSUBTEXTFIELD;
[previouslyCreatedButton addSubview:tf];
[tf release];
I defined TAGOFBUTTONSUBTEXTFIELD as 99 somewhere. Global. It's ugly but...
Then later, to get that text string use something like this:
+(NSString*)getStoredStringFromButton:(UIButton*)button {
UITextField* tf = (UITextField*)[button viewWithTag:TAGOFBUTTONSUBTEXTFIELD];
return tf.text;
}
So this assumes that no one else tries to add a subview with tag 99 to the button.
Lol :-)
That exception is because the actual button that you are trying to get the annotation from is not of class MyDetailButton, it is a UIButton. Verify that you set the class in IB for that particular button. Select the button in IB and press ⌘4 to see its identity, change the Class Identity to MyDetailButton.
Just making a subclass isn't enough; the subclass does not take the place of its superclass. The same way not all UIControls, not all UIViews, not all UIResponders, and not all NSObjects have the behavior of UIButton, not all UIButtons have the behavior of your custom subclass.
What you need is an instance of your subclass. What you have is an instance of UIButton.
The solution is to make that instance an instance of your subclass instead. If you created the button in Interface Builder, select the button and press ⌘6, then change the instance's Custom Class there. If you're creating the button in code, send your alloc message to your custom subclass, not directly to UIButton.

Objective C Delegate for the Main Application Window

So I'm trying to do this exercise where I need to set a delegate for the main window. The purpose is to make sure that when the user resizes the window, it's always twice as wide as it is high.
This is my AppController.h file:
#import <Cocoa/Cocoa.h>
#interface AppController : NSObject
{
NSWindow *windowWillResize;
}
#end
and this is my AppController.m file:
#import "AppController.h"
#implementation AppController
- (id) init
{
[super init];
windowWillResize = [[NSWindow alloc] init];
[windowWillResize setDelegate:self];
return self;
}
- (NSSize) windowWillResize:(NSWindow *)sender
toSize:(NSSize)frameSize;
{
NSLog(#"size is changing");
return frameSize;
}
#end
However, I can remove the line
[windowWillResize setDelegate:self];
since I set the delegate in Interface Builder, but I'm not sure why this works.
How does windowWillResize know that I'm referring to the main application window since I'm doing a completely new
windowWillResize = [[NSWindow alloc] init];
I have a feeling that I am completely doing this wrong. Could someone point me in the right direction? Thanks!
Indeed, you don't need to create a NSWindow *windowWilResize since a newly created Cocoa app already has a main window. You don't need to implement an -init method either.
You only need to set you appController as a delegate of your main window in Interface Builder and to implement the -windowWillResize: method in your appController.
If you are familiar with french language, you can take a look at a blog entry I have written on this subject: Délégation en Cocoa.
You're leaking an instance of NSWindow. In -init you create an NSWindow instance. However, that is not used because when the NIB loads, it sets up all the connections that you specified in Interface Builder and you start using the window from the NIB instead. Do not create a window object in code - Interface Builder does it for you! :-)
In fact, it's not quite "instead"; your app controller is now the delegate for both NSWindow instances - the one that comes from the NIB and the one you instantiated in -init. However as the in-code NSWindow is never used anywhere else, it's still redundant and should be removed.
If you just want to maintain the aspect ratio of the window you can use either of these two NSWindow methods:
setAspectRatio:(NSSize)
setContentAspectRatio:(NSSize)
The first method locks the entire window size, including the title bar. The second one just the content. You can call this method during the initialization of your window inside the delegate (for example: -applicationDidFinishLaunching)