This is the weirdest thing I've ever encountered, and the problem is consistent across an iPhone 4 and iPhone 5s...
I've created a subclass of UITextField to add some custom functionality, and have set a UITextField's custom class to my custom class in the storyboard.
Great, all good - but.. Anytime I tap to open the field, I can type fine - as soon as I hit the emoji globe part of the keyboard, the app instantly locks up. It doesn't crash, I just cannot do anything, the typing indicator stops blinking and the app just hangs infinitely..
So very strange.
Here's the custom class I've created.
Header
#import <UIKit/UIKit.h>
#interface GenericTextField : UITextField <UITextFieldDelegate>
#property id nextField;
#property id nextCallbackTarget;
#property SEL nextCallbackSelector;
#end
Implementation
#import "GenericTextField.h"
#implementation GenericTextField
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder]) {
[self setup];
}
return self;
}
- (void)setup
{
self.delegate = self;
}
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
return YES;
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
if ([self.nextField respondsToSelector:#selector(becomeFirstResponder)]) {
[self.nextField becomeFirstResponder];
} else {
[self resignFirstResponder];
if ([self.nextCallbackTarget respondsToSelector:self.nextCallbackSelector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self.nextCallbackTarget performSelector:self.nextCallbackSelector];
#pragma clang diagnostic pop
}
}
return NO;
}
#end
Here's another interesting part. If I comment out the portion where I'm setting the delegate to self, everything works fine.. However, if I comment out my custom delegate methods and keep the delegate set to self in the setup method, I still get the freeze. This seems to be specifically linked to setting the delegate? Why? Does it have to do with setting it to self from the initWithCoder? If so, why does it just happen in UITextField and none of my other custom classes that set delegates to self like in a UIScrollView subclass I've made?
Alright, took a little searching but maybe this answer will help anyone trying to figure it out.
UITextField is unique AFAIK. You can usually use a class as its own
delegate with no particular problems. For UITextField you must create
an actual delegate (that could, of course, call methods on the
UITextField for which it's a delegate. Just be careful to avoid retain
loops, even if you're using ARC).
Which led me to trying some updated code in my implantation and creating a shared delegate class for handling the delegate in the simplest way I could think of. Here's the updated implementation.
Hope it helps!
#import "GenericTextField.h"
#interface GenericTextFieldDelegate : NSObject <UITextFieldDelegate>
+ (GenericTextFieldDelegate *)sharedDelegate;
#end
#implementation GenericTextFieldDelegate
+ (GenericTextFieldDelegate *)sharedDelegate
{
static GenericTextFieldDelegate *genericTextFieldDelegate;
#synchronized(self) {
if (!genericTextFieldDelegate) {
genericTextFieldDelegate = [[GenericTextFieldDelegate alloc] init];
}
}
return genericTextFieldDelegate;
}
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
return YES;
}
- (BOOL)textFieldShouldReturn:(GenericTextField *)textField
{
if ([textField.nextField respondsToSelector:#selector(becomeFirstResponder)]) {
[textField.nextField becomeFirstResponder];
} else {
[textField resignFirstResponder];
if ([textField.nextCallbackTarget respondsToSelector:textField.nextCallbackSelector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[textField.nextCallbackTarget performSelector:textField.nextCallbackSelector];
#pragma clang diagnostic pop
}
}
return NO;
}
#end
#implementation GenericTextField
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder]) {
[self setup];
}
return self;
}
- (void)setup
{
self.delegate = [GenericTextFieldDelegate sharedDelegate];
}
#end
Related
I have a UIView subclass with a delegate property. In the init method, I set
self.delegate = nil.
The view also has a button, so in the init method, I also set the target of the button to be self.delegate, which is nil:
[myButton addTarget:self.delegate action:#selector(buttonAction) forControlEvents:UIControlEventTouchUpInside]
In the UIViewController that sets up my UIView subclass, I call a method in the UIView that sets the UIView's self.delegate to the UIViewController. When I click the button, the change in target seems to be reflected.
I am wondering how this ends up working, as my understanding is that addTarget:action:forControlEvents takes an id as the target, and pointers should be pass by value in Obj-C. Thus, I am pretty confused about why the originally nil-valued pointer was updated after the addTarget method was already called.
The right way to do that is declaring a protocol for your view, which will delegate for button's tap action, i.e.
YourView.h
#class YourView;
#protocol YourViewDelegate
#optional
- (void)customView:(YourView *)view didSelectButton:(id)button;
#end
#interface YourView : UIView
//...
#property (weak, nonatomic) id <YourViewDelegate> delegate;
#end
YourView.m
#interface YourView()
#end
#implementation
- (id)init
{
if (self = [super init]) {
//...
[self setup];
}
return self;
}
- (void)awakeFromNib
{
//...
// setup logic when this view created from storyboard
[self setup];
}
- (void)setup
{
[myButton addTarget:self
action:#selector(buttonTapped:)
forControlEvents:UIControlEventTouchUpInside];
}
- (void)buttonTapped:(id)sender
{
if (self.delegate && [self.delegate respondsToSelector:#selector(customVIew:didSelectButton)] {
[self.delegate customView:self didSelectButton:sender];
}
}
#end
Then, in your view controller implement YourViewDelegate category:
#interface YourViewController()
//...
#end
#implementation
- (void)viewDidLoad
{
[super viewDidLoad];
//...
self.yourView.delegate = self;
}
//...
- (void)customView:(YourView *)view didSelectButton:(id)button
{
//do your stuff
}
#end
Objective-C uses Dynamic binding. Method to invoke is determined at runtime instead of at compile time. Which is why it is also referred to as late binding.
Reference link -
https://developer.apple.com/library/ios/documentation/general/conceptual/DevPedia-CocoaCore/DynamicBinding.html
So what will be the delegate and which method is being called is defined at runtime.
I'm subclassing UITableView and its protocol UITableViewDelegate and its "datasource" UITableDataSource, everything works fine but I haven't find the clean way to forward the methods from "delegate" and "datasource" from UITableView.
I have tried to "play" with respondsToSelector:, forwardingTargetForSelector: and forwardInvocation: methods of NSObject but I didn't get anything.
I'll show you some code about I'm trying:
My CustomTableView.h:
#protocol TableViewCustomDelegate <UITableViewDelegate>
- (void) anyCustomMethodDelegate;
#end
#protocol TableViewCustomDataSource <UITableViewDataSource>
- (NSInteger) anyCustomMethod;
#end
#interface TableViewCustom : UITableView <UITableViewDelegate, UITableViewDataSource>
#property (nonatomic, weak) id<TableViewCustomDelegate> myDelegate;
#property (nonatomic, weak) id<TableViewCustomDataSource> myDataSource;
#end
And this is my TableViewCustom.m:
#implementation TableViewCustom
-(BOOL)respondsToSelector:(SEL)aSelector {
if ([self.myDelegate respondsToSelector:aSelector]) {
return YES;
}
return [super respondsToSelector:aSelector];
}
-(id)forwardingTargetForSelector:(SEL)aSelector {
if ([self.myDelegate respondsToSelector:aSelector]) {
return self.myDelegate;
}
return [super forwardingTargetForSelector:aSelector];
}
...
...
- (void) setMyDelegate:(id<TableViewCustomDelegate>)delegate
{
[super setDelegate:self];
_myDelegate = delegate;
}
- (void) setMyDataSource:(id<TableViewCustomDataSource>)dataSource
{
[super setDataSource:self];
_myDataSource = dataSource;
}
..
..
// A method from UITableViewDelegate that I would like to avoid
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
if ([self.delegateDelegate
respondsToSelector:#selector(tableView:viewForHeaderInSection:)]) {
return [self.myDelegate tableView:tableView
viewForHeaderInSection:section];
}
return nil;
}
..
..
#end
I would like to avoid conforms methods just to forward it to "myDelegate".
I think it should be work with responsToSelector: and forwardingTargetForSelector: like I do in my code but it doesn't work. I don't know if I'm doing something wrong or if it is not possible.
Thanks in advance.
Regards.
I might be misunderstanding your intent, but if you just want your subclass to utilize the standard delegate and datasource, just leave those methods alone and hand the subclass whatever delegate/datasource you want. e.g.
MyTableViewSubclass *tableView = [[MyTableViewSubclass alloc] initWithFrame:....];
tableView.datasource = // anything that implements UITableViewDatasource
tableView.delegate = // anything that implements UITableViewDelegate
Then don't do anything special with those properties or protocols in your subclass.
In order to make messaging work, you need to implement following methods:
-(void)forwardInvocation:(NSInvocation *)invocation {
SEL aSelector = [invocation selector];
if ([self.myDelegate respondsToSelector:aSelector]) {
[invocation invokeWithTarget:self.myDelegate];
}
}
-(NSMethodSignature*)methodSignatureForSelector:(SEL)selector {
NSMethodSignature *signature = [super methodSignatureForSelector:selector];
if (nil == signature) {
signature = [self.myDelegate methodSignatureForSelector:selector];
}
return signature;
}
Please see the comments lines for explanation
- (void) setMyDelegate:(id<TableViewCustomDelegate>)delegate
{
//you told you will handle delegate methods to SUPER in our case UITableView
[super setDelegate:self];
_myDelegate = delegate; //You have internally stored the delegate
}
In View For Header
//You are overriding this method
//Table View will not handle this as you have overridden.
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
//You check whether your delegate can do that.
if ([self.delegateDelegate respondsToSelector:#selector(tableView:viewForHeaderInSection:)])
{
return [self.myDelegate tableView:tableView
viewForHeaderInSection:section];
}
//If you delegate is not doing, you need to handle that
// However returning nil. So Instead of UIView the framework gets nil
//You need to handle this if your delegate is not handling this
return nil;
}
Let's say for example that I have MyUITextViewSubclass which inherits from UITextView and MyUITextFieldSubclass which inherits from UITextField and both of those subclasses contain a lot of the same methods and properties to add similar behavior to those UI controls.
Since UITextView and UITextField inherit from different classes, is there an easy way to create an abstract class to combine all of that repeated code? In other words, is it possible to create an abstract class that I could inherit from for both of those subclasses and then just override the methods that are different between the two?
What I know so far:
I know Objective-C doesn't support multiple inheritance (inheritance from two or more classes)
I know I could add common methods using Categories, but I don't think that solves overriding init methods or adding private properties
Building on Amin's answer, this is how you could do it:
Step 1: Create a TextSurrogateHosting protocol that will contain all the methods of your UITextField and UITextView subclasses that you need to access from the methods that you want to add to both subclasses. This might for example be a text and setText: method, so that your methods can access and set the text of either a text field or a text view. It might look like this:
SPWKTextSurrogateHosting.h
#import <Foundation/Foundation.h>
#protocol SPWKTextSurrogateHosting <NSObject>
- (NSString *)text;
- (void)setText:(NSString *)text;
#end
Step 2: Create a TextSurrogate class that contains all the methods that you want to share between both the UITextField and the UITextView subclasses. Add those methods to a protocol so that we can use code completion in Xcode and avoid compiler warnings/errors.
SPWKTextSurrogate.h
#import <Foundation/Foundation.h>
#import "SPWKTextSurrogateHosting.h"
#protocol SPWKTextSurrogating <NSObject>
#optional
- (void)appendQuestionMark;
- (void)appendWord:(NSString *)aWord;
- (NSInteger)characterCount;
- (void)capitalize;
#end
#interface SPWKTextSurrogate : NSObject <SPWKTextSurrogating>
/* We need to init with a "host", either a UITextField or UITextView subclass */
- (id)initWithHost:(id<SPWKTextSurrogateHosting>)aHost;
#end
SPWKTextSurrogate.m
#import "SPWKTextSurrogate.h"
#implementation SPWKTextSurrogate {
id<SPWKTextSurrogateHosting> _host;
}
- (id)initWithHost:(id<SPWKTextSurrogateHosting>)aHost
{
self = [super init];
if (self) {
_host = aHost;
}
return self;
}
- (void)appendQuestionMark
{
_host.text = [_host.text stringByAppendingString:#"?"];
}
- (void)appendWord:(NSString *)aWord
{
_host.text = [NSString stringWithFormat:#"%# %#", _host.text, aWord];
}
- (NSInteger)characterCount
{
return [_host.text length];
}
- (void)capitalize
{
_host.text = [_host.text capitalizedString];
}
#end
Step 3: Create your UITextField subclass. It will contain three necessary boilerplate methods to forward unrecognized method invocations to your SPWKTextSurrogate.
SPWKTextField.h
#import <UIKit/UIKit.h>
#import "SPWKTextSurrogateHosting.h"
#import "SPWKTextSurrogate.h"
#interface SPWKTextField : UITextField <SPWKTextSurrogateHosting, SPWKTextSurrogating>
#end
SPWKTextField.m
#import "SPWKTextField.h"
#implementation SPWKTextField {
SPWKTextSurrogate *_surrogate;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
_surrogate = [[SPWKTextSurrogate alloc] initWithHost:self];
}
return self;
}
#pragma mark Invocation Forwarding
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([_surrogate respondsToSelector:[anInvocation selector]]) {
[anInvocation invokeWithTarget:_surrogate];
} else {
[super forwardInvocation:anInvocation];
}
}
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
NSMethodSignature* signature = [super methodSignatureForSelector:selector];
if (!signature) {
signature = [_surrogate methodSignatureForSelector:selector];
}
return signature;
}
- (BOOL)respondsToSelector:(SEL)aSelector
{
if ([super respondsToSelector:aSelector] ||
[_surrogate respondsToSelector:aSelector])
{
return YES;
}
return NO;
}
#end
Step 4: Create your UITextView subclass.
SPWKTextView.h
#import <UIKit/UIKit.h>
#import "SPWKTextSurrogateHosting.h"
#import "SPWKTextSurrogate.h"
#interface SPWKTextView : UITextView <SPWKTextSurrogateHosting, SPWKTextSurrogating>
#end
SPWKTextView.m
#import "SPWKTextView.h"
#import "SPWKTextSurrogate.h"
#implementation SPWKTextView {
SPWKTextSurrogate *_surrogate;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
_surrogate = [[SPWKTextSurrogate alloc] initWithHost:self];
}
return self;
}
#pragma mark Invocation Forwarding
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
if ([_surrogate respondsToSelector:[anInvocation selector]]) {
[anInvocation invokeWithTarget:_surrogate];
} else {
[super forwardInvocation:anInvocation];
}
}
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector
{
NSMethodSignature* signature = [super methodSignatureForSelector:selector];
if (!signature) {
signature = [_surrogate methodSignatureForSelector:selector];
}
return signature;
}
- (BOOL)respondsToSelector:(SEL)aSelector
{
if ([super respondsToSelector:aSelector] ||
[_surrogate respondsToSelector:aSelector])
{
return YES;
}
return NO;
}
#end
Step 5: Use it:
SPWKTextField *textField = [[SPWKTextField alloc] initWithFrame:CGRectZero];
SPWKTextView *textView = [[SPWKTextView alloc] initWithFrame:CGRectZero];
textField.text = #"The green fields";
textView.text = #"What a wonderful view";
[textField capitalize];
[textField appendWord:#"are"];
[textField appendWord:#"green"];
[textField appendQuestionMark];
NSLog(#"textField.text: %#", textField.text);
// Output: The Green Fields are green?
[textView capitalize];
[textView appendWord:#"this"];
[textView appendWord:#"is"];
NSLog(#"textView.text: %#", textView.text);
// Output: What A Wonderful View this is
This pattern should solve your problem. Hopefully :)
Some more background information is available here: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtForwarding.html#//apple_ref/doc/uid/TP40008048-CH105
What you want is a mixin. This is not supported in Objective-C. Categories are no mixins, because they add an api to one class not to many (>1) classes. Using categories, what is not possible for many reasons as you said, would not help you.
The usual way to solve that problem is to create a helper class containing the additional code and use it in both classes.
Then you will find yourself typing
[myUITextViewSubclass.helper doSomething]
instead of
[myUITextViewSubclass doSomething]
If this is really a problem, you can solve this with forward invocations. Just write a comment.
No. It is not possible.
The closest thing you could achieve would be to manually add functionality to UITextView to make it mimic UITextField. The obvious downside is that you must do this all manually, with your own code.
You could use a preprocessor macro, but that is error-prone.
Traits or Mixins are not supported by Objective-C, you only have built-in option of Categories.
But fortunately Objective-C Runtime has almost all tools for implementing own idea if mixing or traits with adding methods and properties to your class at runtime. You can read more about opportunities which Objective-C Runtime provides for you on Apple's documentation website Objective-C Runtime Docs
The idea is:
1) You can create an Objective-C protocol (Mixin), in which you will declare properties and methods.
2) Then you create a class (Mixin implementation), which will implement methods from this protocol.
3) You make your some class, in which you want to provide the possibility of composition with mixins, to conform that protocol (Mixin).
4) When your application launches, you add with Objective-C runtime all implementations from (Mixin implementation) class and properties declared in (Mixin) into your class.
5) voilĂ :)
Or you can use some ready open source projects such as "Alchemiq"
I'm trying to move the view up when the keyboard shows so it wont cover up the screen, but for some reason its the -(void)DidBeginEditing: (UITextField *)textfield is not working.
- (void)textFieldDidBeginEditing:(UITextField *)ga1
{
/* should move views */
self.view.center = CGPointMake(self.view.center.x, self.view.center.y + 220);
}
- (void)textFieldDidEndEditing:(UITextField *)ga1
{
/* should move views */
self.view.center = CGPointMake(self.view.center.x, self.view.center.y - 220);
}
its nor going into the method, can anyone tell me why?
In the interface of the class add the line , so in the .m file you would put above where it says #implementation...
#interface MyClassName () <UITextFieldDelegate>
// properties can also go here
// for example dragging the IBOutlet for the textfield from the storyboard
#end
You then in viewDidLoad should set the delegate of the UITextField like so...
-(void)viewDidLoad {
// whatever code
self.textField.delegate = self;
}
Alternatively, and more cleanly, you can do this in the story board by control clicking the text field and dragging the indicator to the view controller class icon (the icon to the furthest left) in the lower bar.
Also, why are you calling the argument to the textField in your implementation "ga1"? Best practice you should call it
- (void)textFieldDidBeginEditing:(UITextField *)textField
One final note is that if you have multiple textFields you should set the delegate for each of them in the way described above. This is why the storyboard way of doing it is "cleaner," because it keeps you from having multiple delegate declarations in your code.
If implemented, it will get called in place of textFieldDidEndEditing
#interface MyViewController :UIVieController <UITextFieldDelegate>
#property UITextField *myTextField
#end
#implementation MyViewController{
}
- (void)viewDidLoad {
[super viewDidLoad];
self.myTextfield.delegate=self;
}
-(void)textFieldDidEndEditing:(UITextField *)textField reason:(UITextFieldDidEndEditingReason)reason{
if(reason==UITextFieldDidEndEditingReasonCommitted)
{
NSLog(#"Committed");
}
}
Implement <UITextFieldDelegate> Protocol for your class. And set the delegate to self. Here is Apple's document UITextFieldDelegate Protocol
I have an ivar which is mentioned in my header
#interface MyClass : UIView{
int thistone;}
- (IBAction)toneButton:(UIButton *)sender;
#property int thistone;
#end
and I have synthesized it in the implementation:
#implementation MyClass
#synthesize thistone;
- (IBAction)toneButton:(UIButton *)sender {
if(thistone<4)
{thistone=1000;} // I hate this line.
else{thistone=thistone+1; }
}
I cannot find (or find in any manual) a way to set a nonzero initial value. I want it to start at 1000 and increase by 1 each time I press the button. The code does exactly what I intend, but I'm guessing there's a more proper way to do it that saves me the if/else statement above. Code fixes or pointers to specific lines in online documentation greatly appreciated.
Every object has a variant of the init method called at instantiation. Implement this method to do such setup. UIView in particular have initWithFrame: and initWithCoder. Best to override all and call a separate method to perform required setup.
For example:
- (void)commonSetup
{
thisTone = 1000;
}
- (id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
[self commonSetup];
}
return self;
}
- (id)initWithCoder:(NSCoder *)coder
{
if (self = [super initWithCoder:coder])
{
[self commonSetup];
}
return self;
}