Apologies for this really basic question, but in my books and online tutorials I cannot find an example of how to subclass a custom view controller - what exactly has to be written down and what we can get from inheritance. Based on experience in other languages, I thought I'd get everything from the parent class in my subclassed UIViewController without having to re-code it. I thought I could just modify the functions I wanted to modify, but this appears not to be the case. Description of what I tried below:
Already I have a customer UIViewController called SignupViewController. Now I want to add a view where the user can update her info, and I realized it would basically be a remake of SignupViewController except with a few UITextFields hidden and all UITextFields prepopulated with existing information. I thought I could subclass the way I would subclass a normal UIViewController, but I find that when I try to override methods in SignupViewController in the new subclass of SignupViewController called UpdateViewController, I am told that the properties are not found. It almost feels as though I am working with a blank file and that the properties of SignupViewcontroller have not been referenced. For example, I'm trying to cull back the fields so I'm modifying this code in UpdateViewController:
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
CGFloat lineHeight = .05*_height;
if (textField == self.firstName) {
[textField resignFirstResponder];
[self.lastName becomeFirstResponder];
} else if (textField == self.lastName) {
[textField resignFirstResponder];
[self.emailAddress becomeFirstResponder];
[self.scrollView setContentOffset: CGPointMake(0, lineHeight)animated:YES];
} ...etc...
}
return YES;
}
However I am getting errors that UpdateViewController (inheriting from SignupViewController) has no scrollView, emailAddress, firstName, lastName, etc property. Do I have to redeclare all these properties/write everything out? If so, what does subclassing really mean?
Here's what the .h and .m files of the subclass of the SignupViewController look like:
.h
#import "SignupViewController.h"
#interface UpdateViewController : SignupViewController
#end
.m
#import "UpdateViewController.h"
#interface UpdateViewController ()
#end
#implementation UpdateViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
CGFloat lineHeight = .05*_height;
if (textField == self.firstName) {
[textField resignFirstResponder];
[self.lastName becomeFirstResponder];
} else if (textField == self.lastName) {
[textField resignFirstResponder];
[self.emailAddress becomeFirstResponder];
[self.scrollView setContentOffset: CGPointMake(0, lineHeight)animated:YES];
} ...
return YES;
}
#end
If someone can point me to a resource that spells this all out I'd really appreciate it. I'm sure it's in the docs, but I have not been able to find it.
I am getting errors that UpdateViewController (inheriting from SignupViewController) has no scrollView, emailAddress, firstName, lastName, etc property. Do I have to redeclare all these properties/write everything out?
If the properties are declared in SignupViewController.m, then they will be invisible to all code outside SignupViewController.m. This is intentional and he most common way to declare properties.
If you want them to be public, you must declare the properties in SignupViewController.h, and remove them from the .m file.
Anything in a .m file is only available from within that file, if you want it available elsewhere you must put it in a .h file and import the .h file. It's not a common programming language feature today but in older programming languages (and Obj-C is aproaching 30 years) that is generally how all of them work.
Related
I have a UITextView which I am using as text entry in a chat application. When the user presses return then I want to do some action e.g. save the chat message.
I haven't been able to find a solution that allows me to do this (lots for TextFields but not for TextView).
Here is the solution I am trying at the moment, which seems to be the most obvious I can find, but it isnt working, in debug I see that the method isn't touched:
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if ([text isEqualToString:#"\n"]) {
NSLog(#"Return pressed");
} else {
NSLog(#"Other pressed");
}
return YES;
}
In my chat .h file:
#interface ChatTableViewController : UITableViewController <UITextViewDelegate>
and viewdidload .m file
- (void)viewDidLoad {
[super viewDidLoad];
enterText.delegate = self;
// more
}
Incidentally if there is a better field to use as input in a chat program than UITextView please let me know.
Many thanks
... but it isn't working, in debug I see that the method isn't touched
Your problem obviously is that the delegate method is not called. Fix the delegate and use the code you already have: it's good.
Try this way. No need to go with delegates.
Add this event responder at where you initialize the textView.
[theTextView addTarget:self
action:#selector(targetMethodToPerformCustomOperation)
forControlEvents:UIControlEventEditingDidEndOnExit];
Here is my .h file
#import <UIKit/UIKit.h>
#interface PersonViewController : UIViewController
#property(strong,nonatomic) NSString *personTitle;
And here is my .m file
#interface PersonViewController ()
#property (weak, nonatomic) IBOutlet UILabel *titleView;
#end
#implementation PersonViewController
//stuff …
-(void)setPersonTitle:(NSString *)personTitle
{
[self.titleView setText:personTitle];// also self.titleView.text=personTitle
[self.titleView setNeedsDisplay];
NSLog(#"The title shoud match as %# :: %#",personTitle,self.titleView.text);
}
-(NSString *)personTitle
{
return self.titleView.text;
}
//… more stuff
#end
The logging shows that the value is (null) for self.titleView.text whereas personTitle prints the appropriate value.
I remember doing this same thing a number of times and it worked. Any ideas why it’s failing this time?
update I use storyboard to set my scenes. And I am using xcode-5 and iOS-7
update: how I call
The user clicks a button, leading to a push segue
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSLog(#"enter prepare for segue.");
NSIndexPath *indexPath = [self.tableView indexPathForCell:sender];
if ([segue.identifier isEqualToString:the_identifier_for_person]) {
NSLog(#"segue to person is progressing“);
if ([segue.destinationViewController isKindOfClass:[PersonViewController class]]) {
NSLog(#"segue to person destination is a match");
PersonViewController *aPerson = (PersonViewController *)segue.destinationViewController;
aPerson.personTitle=((MyItem*)self.allItems[indexPath.row]).title;
NSLog(#"segue to person is done");
}
}
}
This sounds like you forgot to wire up your UILabel in the storyboard. Can you confirm that self.titleView is not null?
View controllers create their views on demand, but can spot that only via a call to view. When the view is loaded, your outlets will be populated.
Either call view to force loading or keep the string in abeyance until you get viewDidLoad.
(aside: prior to iOS 6, views would also be released in low-memory situations so the idiomatic thing is to store the string and populate on viewDidLoad)
Having accepted another answer, I wanted to show the pattern that I actually used to solve the problem, in case someone else comes looking. This pattern is best practice (yes, I forgot it for a long moment there).
#pragma mark - update UI
-(void)setPersonTitle:(NSString *)personTitle
{
_personTitle=personTitle;
if (self.view.window) [self updateUI];//only if I am on screen; or defer to viewWillAppear
}
-(void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self updateUI];
}
-(void)updateUI
{
self.titleView.text=self.personTitle;
}
It is always important to update the ui when the data has changed, which is why I must make the call inside setPersonTitle. But because the IBOutlets are not yet set when I set personTitle in prepareForSegue, then I must also make the call inside viewWillAppear.
Do you actually call the -(void)setPersonTitle:(NSString *)personTitle method?
It seems that you aren't calling it correctly which would result in the title being null.
After reviewing the prepareForSeque it is clear that you are not calling the method. You are actually just changing the #property named personTitle.
In the viewDidLoad you should have it so that self.titleView.text = self.personTitle;
The question: How do I prevent the copy/paste/select popup that occurs over a UITextView from appearing (not using UIwebView and css)?
I did not want to go the rout of UIWebView as some posts have gone because I already am using UIViews with UITextFields for data entry. I had tried unsuccessfully to implement the solutions dealing with UITextField in my implementation file of my view controller with the methods: targetForAction:withSender, setMenuVisible:animated and finally canPerformAction:withSender. (It NO WORKY WORKY - [sad face])
Ok, I found a working solution (in Xcode 5.1) to my question which, in short, is subclassing the UITextField.
I realized I wasn't really overriding the default behavior of the UITextField in the view controller like I wanted to and neither was putting the methods listed here override the behavior of the textfield delegate in the view controller file. The Key was to subclass the UITextField itself with -targetForAction:withSender. (I know some of you are screaming at the screen about how OBVIOUS that was!) It was not obvious to me. Like most problems when first figuring them out I went through a lot of different paths some I found here in SO. But the solution is a simple one. I want to share this solution in its own area so hopefully it can help someone out.
The header file:
//
//
#import <UIKit/UIKit.h>
#interface TPTextField : UITextField
- (id)targetForAction:(SEL)action withSender:(id)sender;
#end
and the implementation file (.m)
//
//
#import "TPTextField.h"
#implementation TPTextField
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
#pragma mark - method overrides - deny user copy/paste on UITTextFields
- (id)targetForAction:(SEL)action withSender:(id)sender
{
UIMenuController *menuController = [UIMenuController sharedMenuController];
if (action == #selector(selectAll:) || action == #selector(paste:) ||action == #selector(copy:) || action == #selector(cut:)) {
if (menuController) {
[UIMenuController sharedMenuController].menuVisible = NO;
}
return nil;
}
return [super targetForAction:action withSender:sender];
}
#end
In your storyboard or nib/xib file just connect this class to your UITextfield like the picture below:
I have it on git to for easy access here. Please let me know if this is helpful to you!
Tony
If the UITextView is created as an object on a storyboard, the solution is even easier. In Attributes Inspector for the UITextView object, under Behavior, uncheck Editable and uncheck Selectable. Under the Scroll View section, you can check Scrolling Enabled if you want the user to be able to scroll text.
I've just started with xcode and objective-c and did some very basic apps, but what i'm having problem with is very basic this. the keyboard return button not hiding the keyboard.
I've searched the internet for the solution and all they say is to connect delegate to the file's owner and add the function and it should work, i did that and nothing is working.
I have an ok button and it is working and also clicking on any free space on the screen is working, just the return button....
I am using the simulator, not testing on iphone yet. (xcode 3.2.5 64 bit with the 4.2 simulator).
This is the line of code that should connect the delegate to every textFiled.
1. i've tried already to return both YES and NO, didn't work.
2. i've tried both a specific object name for the textField and this general way, didn't work.
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
return NO;
}
In the: basic view controller connection -> connections -> outlets, i have the: delegate -- File's Owner. and in the file's owner in referencing outlets there is: delegate - Round style text.....
EDIT - i forgot to mention before, i've check and the method isn't being called!!!
- (BOOL)textFieldShouldReturn:(UITextField *)textField{
NSLog(#"Working!!!");
[textField resignFirstResponder];
return YES;
}
what should i do to make it happen? that is why people say to connect the delegate, but in my case it is connected and not triggering the function...i know it is kind of dumb question but for a nobie like me the solution is not obvious...
OK, another Edit - with all my code: just can't understand what to do....
This is: basicViewController.h:
#import <UIKit/UIKit.h>
#interface basicViewController : <#superclass#> <UITextFieldDelegate>
#interface basicViewController : UIViewController <UITextFieldDelegate> {
//every object that we want to interact with (like text field or lable) is call an outlet!!!!
//here we define the outlets for our program
IBOutlet UITextField *txtName;
IBOutlet UILabel *lblMessage;
}
//here are the getters and setter for our outlets
#property (nonatomic, retain) IBOutlet UITextField *txtName;
#property (nonatomic, retain) IBOutlet UILabel *lblMessage;
//method decleration for the OK button action
- (IBAction) doSomething;
//method for hiding the keyboard when clicking on empty area in the app
//we will put an invisible button on all area and clicking on it will make keyboard disapear
- (IBAction) makeKeyboardGoAway;
#end
This is basicViewController.m:
#import "basicViewController.h"
#implementation basicViewController
//synthesizeing the objects that we made' this will create the getter and setters automaticly
#synthesize txtName;
#synthesize lblMessage;
- (IBAction) doSomething{
// makeing keyboard disapear when pressing ok button (doing that form the text field)
//when pressing the OK button, the keyboard will disapear and when clicking in the text field it will show again
[txtName resignFirstResponder];
NSString *msg = [[NSString alloc] initWithFormat:#"Hello, %#",txtName.text];
//the objective-c way for setting the test in the text field
[lblMessage setText:msg];
//the regular object oriented way
//lblMessage.text = msg;
[msg release];
}
- (IBAction) makeKeyboardGoAway{
[txtName resignFirstResponder];
}
//when clicking the return button in the keybaord
- (BOOL)textFieldShouldReturn:(UITextField *)textField{
NSLog(#"Working!!!");
[textField resignFirstResponder];
return YES;
}
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
- (void)viewDidUnload {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[super dealloc];
}
#end
Maybe now i am more clear, sorry i didn't do it before.
Any one has an idea what am i doing wrong? it should be pretty strait forward.....
EDIT - Adding an image of all the elements, i hope that will help to help me :-)
10x a lot for every one that is trying to help....i really like this framework, it is so great after c++ and java, python and many other...and i am working with a book, but it is for ios 3.1, maybe that is the problem.....
Firstly you should check if textFieldShouldReturn: is actually being called by adding an NSLog statement or breakpoint at the beginning of the method.
Once that's out of the way, try an manually declare that your view controller conforms to <UITextFieldDelegate> protocol in your interface file:
#interface YourClass : ... <UITextFieldDelegate>
Also declare a property & outlet for your UITextField, make the appropriate connections in IB and manually declare self as the UITextField delegate with:
self.yourUITextFieldObject.delegate = self;
Once that's done see if your method above is now being called and make sure you return YES.
Just write one line in the
-(BOOL)textFieldShouldReturn:(UITextField *)textField{
}
before return YES;
the final version will be as given below:
-(BOOL)textFieldShouldReturn:(UITextField *)textField{
[textField resignFirstResponder];
return YES;
}
-(void)textFieldDidEndEditing:(UITextField *)textField{
NSLog(#"%#",textField.text);
}
You need to assign the delegate of the textfields to your file owner. The textfields are sending the message, but doesn't have a delegate to respond to it.
Use the interface builder to do that.
You have to implement this method..
-(BOOL)textFieldShouldReturn:(UITextField *)textField
{
[textField resignFirstResponder];
return YES;
}
Like Rog said, don't forget to register the textfield to the delegate, you can do this manually as he said but in Storyboard you can just control drag from all of your textfields to the view controller and register the delegate (choose delegate). Only the textfields that are registered can make use of all those methods.
So this line is important:
self.yourUITextFieldObject.delegate = self;
Or even more easy these days is to just use the storyboard:
textfield is in a subview? in this case, make sure textfield have as delegate the FileOwner.
put a log at the
- (IBAction) makeKeyboardGoAway
function. I think its this method everytime anything is tapped on the screen. In that case, you will need to send the touch event to the text field. Not sure how this is done but that should do it.
Else try removing the which takes care of tap(click) all over the view and try to do what you are doing.
Most likely the problem is that your actual view controller in the running application is not a "basicViewController" but a UIViewController that does not implement the UITextFieldDelegate-protocol.
What you've done in the interface builder by selecting your class "basicViewController" as the FilesOwner is just declaring the FilesOwner-object in your running application to be of type basicViewController; the actual object is not instantiated by this declaration and in your case it is not in the xib / nib.
Some other part of your code actually instantiates a view controller object and loads the xib / nib file. At that place, I guess your code is instantiating a UIViewController (typically by auto-generated code) and not an instance of your basicViewController; you simply have to change the class there.
Furthermore, this error often happens when using a UINavigationController or UITabBarController in the Interface Builder that is (should be) configured to instantiate and load other custom views. If you use such a higher-level controller, double-check that it is actually configured to use your basicViewController, not UIViewController when loading your view from the xib / nib.
Hope, that solves the issue!
Can you try this..
#interface ClassName : SuperClass < UITextFieldDelegate >
Use like this...
textfield.delegate=self;
and use the UITextFieldDelegate in .h class
You can always dismiss the keyboard when you don’t even know which view the text field is in by using:
Objective-C:
[[UIApplication sharedApplication] sendAction:#selector(resignFirstResponder)
to:nil
from:nil
forEvent:nil];
Swift:
UIApplication.sharedApplication().sendAction("resignFirstResponder",
to:nil,
from:nil,
forEvent:nil)
I have a simple request that I have spent much time on (embarrassingly)..
I have sub-classed a UITableView to add some functionality. These new features require things like NSMutableSet which require allocation/initialization.
I have put my object's initialization routine in
- (id)initWithFrame:(CGRect)frame style:(UITableViewStyle)style {
which I understood from the apple docs to correct - but this doesn't get called (determined by break-pointing on the code).
I am using IB, and have dragged a UITableView onto the view, and changed it's class to my new sub-class. There is no UITableViewController.
I have also tried:
- (void)loadView {
- (id)init {
- (id)initWithFrame:(CGRect)frame {
with no success. I would like to have this class work both with IB, and programmatically in the future. Everything works apart from the location of this initialization..
When objects load from a XIB file, they get -(id)initWithCoder:(NSCoder*)coder.
If you create objects from XIBs and programmatically, you'll need to implement both the designated initializer -initWithFrame:style: and -initWithCoder:, doing all your init stuff in each one.
Keeping those two in sync can be a pain, so most folks like to break the init stuff out into a private method, typically called -commonInit.
You can see an example of this in action in some of the Apple sample code: HeadsUpUI.
- (void)commonInit
{
self.userInteractionEnabled = YES;
}
- (id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
[self commonInit];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder])
{
[self commonInit];
}
return self;
}
One common mistake that people make when they're new to Cocoa or Cocoa Touch, is to subclass when they don't actually need to. I've seen many examples of custom windows, tableviews, scrollviews and imageviews that need never have been written.
What functionality are you adding to UITableView? Are you sure that what you want to do can't be accomplished through the delegate methods, or by using a custom cell class?