Beginner programmer trying to understand why I cannot accomplish this simple task. I know it is most likely a very simple solution but hoping that someone will explain the WHY.
I have a screen where users can input emails into a textfield. The idea is that if upon entering the e-mail, if it is not already in the stored emails, it will prompt the user to enter the new email/contact into the store. Since I check if the e-mail is valid BEFORE popping up the screen for contact creation, I'd like to take the text entered and put it directly into the "Email" field on the new contact creation page and not allow editing. I've tried numerous methods but CANNOT get the email to show up in the text field. Can someone explain why this is?
Code from initial VC where users enter their emails. If the email does not exist in the store, this code creating the contact creator page is fired:
//I created this custom initializer since setting the text field (as I did below) would not work
ContactCreationViewController *contactCreationVC = [[ContactCreationViewController alloc] initWithEmail:trimmedText];
contactCreationVC.delegate = self;
//initially I tried setting the text here but it did not work, I now understand why
//[contactCreationVC.emailField setText:#"asdsadasd"];
[contactCreationVC setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
[contactCreationVC setModalPresentationStyle:UIModalPresentationOverCurrentContext];
[self presentViewController:contactCreationVC animated:YES completion:nil];
This is the code for the actual ContactCreatorVC:
-(instancetype) initWithEmail:(NSString*)email{
self = [super init];
//tried setting email here which works as I check with breakpoints
[self setEmail:email];
//self.email = email here when I check
return self;
}
....
- (nonnull UITableViewCell *)tableView:(nonnull UITableView *)tableView cellForRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
switch (indexPath.row) {
....
case 3: {
ContactCreationTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:contactCreationCellIdentifier];
cell.titleLabel.text = #"E-Mail";
cell.userEntryTextfield.tag = 3;
cell.userEntryTextfield.delegate = self;
cell.userEntryTextfield.keyboardType = UIKeyboardTypeEmailAddress;
//I try setting the email here but self.email = nil (don't understand why)
cell.userEntryTextfield.text = [NSString stringWithFormat:#"%#", self.email];
cell.userEntryTextfield.userInteractionEnabled = NO;
cell.userEntryTextfield.autocapitalizationType = UITextAutocapitalizationTypeNone;
self.emailField = cell.userEntryTextfield;
return cell;
}
.....
}
I feel stupid for even asking such a simple question but I clearly am not understanding what is going on behind the scenes cause I've tried everything. Can anyone explain what is going on and suggest the ideal solution from a best practice standpoint?
EDIT: I guess my question could be more concise...it basically boils down to: why when I set self.email in the init does it not stick when I access self.email in the cellForRow method (it becomes nil)?
(the textfield is in a cell in a tableview)
The problem is this line:
[contactCreationVC.emailField setText:#"asdsadasd"];
You have only just created contactCreationVC. Its view has not loaded, so emailField is nil (easily demonstrated by some rudimentary logging). A message to nil does nothing.
The correct approach is: never touch another view controller's outlets. Set a property of the other view controller and let it deal with its own outlets. In this case, the other view controller would need to use the property to set the emailField text in its viewDidLoad.
As to why that approach doesn't work for you, there isn't enough info to answer it as far as I can tell. If you can prove that things go in this order:
A ContactCreatorVC init is called with an actual email value, and the property is set.
The property has a strong (retain) policy so the value is retained.
The very same ContactCreatorVC instance now has its cellForRowAtIndexPath called, and that moment the property is nil.
If you can prove all that, then the only logical conclusion is that meanwhile some other code has come along and set the property to nil.
Related
So I am implementing a custom navigation item in my view controller via the method like this
-(UINavigationItem*)navigationItem{
item = [[SearchNavigationItem alloc] init];
item.delegate = self;
return item;
}
The SearchNavigationItem will set itself up, add a UITextField and so on.
The field.delegate will have the item as the delegate.
So the issue I have is that when I try to grab the text of the field, it is nil. But when the "textfield changed" is called, I can access the field via the argument (textFieldDidChange:UITextField*) and it has the text.
Another issue, like the title, was that when I did [field resignFirstResponder] nothing happened.
Okay so I already have the answer, and I am writing this question because I could personally not find any help while fixing it.
So the issue is that navigationItem can be called multiple times and this kept creating new bars.
So the solution became, simply, this:
-(UINavigationItem*)navigationItem{
// Apparently it should be treated as a 'singleton' which I think it says
// kind of in the documentation. This comment is just to reinforce that
// it burned me to init it each time this method is called. Which is can
// be multiple times and also outside of the class itself (like when nav'ing)
if(item == nil){
item = [[SearchNavigationItem alloc] init];
item.delegate = self;
}
return item;
}
Hope this helps someone else.
Still fairly new to iOS. I've managed to write a basic app to display a list/table of documents. I've included:
cell.accessoryType = UITableViewCellAccessoryDetailButton;
which display a blue i within a circle button which I guess can be used to perform some sort of action.
What I'd like to do now is, upon click, to display a subsequent screen with information about the document, buttons, add, delete functions, date, file size etc...
Is this done via segue or some other method?
Being a novice I am not sure what is/are the next step(s). If I know what steps I must take in order to get to next scree(s) I can search the net for example of how to do any given step.
Thank you for your help
The accessoryButton triggers it's own delegate method, distinct from the row selection, called:
- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath
Add that method to your delegate, and then do a vc transition. There are several ways to do that transition, most of the common ones are discussed here...
First, welcome to the iOS developing community!
In order to do this, I would use a segue to a detail view that you can design. When you select a row in your table using didSelectRowAtIndexPath, you would set an #property to the object in your array that was selected: self.selectedObject = self.tableviewarray objectAtIndex:indexPath.row; Then in the prepareForSegue method you can get the destination view controller and do destinationViewController.myObject = self.selectedObject; Now the detail view knows what object to display info for!
You'll need to implement the delegate method
-tableView:(UITableView*) didSelectRowAtIndexPath:(NSIndexPath*)
and handle the row tap in that callback accordingly.
An example would be like this:
-tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
YourObject *theModelForRow = [_itemList objectAtIndex:indexPath.row];
YourViewController *someNewViewController = [YourViewController viewControllerWithModel:theModelForRow];
[self.navigationController pushViewController:someNewViewController animated:YES];
}
So, I have one ViewController that has a TableView populate with an Array (_vinhoArray) and another TableViewController that opens when a user taps a row in this first ViewController.
What I want is set the title of second View ( TableViewController) for the name of row selected.
In this first one I have this code.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *selectedRow = [_vinhoArray objectAtIndex:indexPath.row];
// SubTable is the name of Second View
SubTable *subTable = [[SubTable alloc] init];
subTable.titleView = selectedRow;
}
In the second view a have a property set
#property (nonatomic, strong) NSString *titleView;
In the -(void)viewDidLoad I have this code
self.title = titleView;
But nothing shows in the title of the Second View (TableViewController)
Thats it! Please Help
If you simply want to set the title on the navigation bar of the view controller you are presenting, there's no need to create a property for it. You probably want to do something like this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *titleString = [_vinhoArray objectAtIndex:indexPath.row];
// SubTable is the name of Second View
SubTable *subTable = [[SubTable alloc] init];
subTable.title = titleString;
[self.navigationController pushViewController:subTable animated:YES];
}
Above seems to be the easiest and most straight-forward way to implement this. On a side note, it's pretty unconventional to name a variable type like an NSString with some other class type, like titleView or titleLabel. Something similar to "titleString", like I used, would be more appropriate and less confusing for anyone reading your code.
What is probably happening is that the view is loading before you set the titleView property. As you are setting the title statically in viewDidLoad, no changes are being shown. You can confirm this by putting in a couple of breakpoints and seeing the order of the calls being made.
One solution to this is to use a more dynamic method of setting the table. Create a custom accessor in your second view controller:
- (void)setTitleView:(NSString *)title {
_titleView = title; // This sets the backing iVar
self.title = titleView;
}
Now, whenever you set the property, then the change is applied, even if it is after the view has loaded.
You don't need to declare the method or the iVar, these are set up by autosynthesis; you are merely overriding the default implementation of the property setter.
Are you certain that setting .title property is what you want? If you're in a UINavigationController you may actually want
self.navigationItem.title
instead. Setting the title is independent of source of the title, so I'd work on getting that second View Controller to display a static title you choose, and only then turn to the task of passing a title dynamically from the first VC. Your issue is likely in setting the title string, not in how you pass that string around from VC 1 to VC 2.
As obvious as it seems, the .title property of a UIViewController doesn't always actually cause a title string to be displayed… the rules for when that property is actually used get tricky.
I am assuming that "titleView" is nothing but of type NSString.
In your SecondViewController.h, synthesize your titleView property.
And NSLog it in your SecondViewController before assigning it to self.title just to check that it's getting some value we tried to assign.
Hope this helps!
I created a Master-Detail-Application, which uses one DetailViewController and multiple TableViewDataSources. Every time the user touches an item, i check the items class and choose the right TableSource for it.
Just like this:
if ([_detailItem isKindOfClass: [cAdress class]]) {
self.dataSource = [[AddressDetailTableSource alloc] init];
((AddressDetailTableSource *) dataSource).current = _detailItem;
} else if ([_detailItem isKindOfClass: [cActivities class]]) {
self.dataSource = [[ActivityDetailTableSource alloc] init];
((ActivityDetailTableSource *) dataSource).current = _detailItem;
}...
Sometimes i go more into Detail and push a new DetailView above the current DetailView. I do this a lot with some different views. Choosing an item in the MasterView causes, that the application goes back to the first DetailView (popToRootViewController).
I now have a problem with one view in particular. When this view is on Top and i choose an item in the MasterView, my App crashes. With NSZombies i found out, that it still tries to build the table with the wrong DataSource. Or at least it tries to call "titleForHeaderInSection" on the wrong DataSource. The error message is:
[ItemDetailTableSource tableView:titleForHeaderInSection:]:message sent to deallocated instance...
The error only occurs with this specific TableSource, also i treat same all the same.
Can anyone help me to get rid of this problem?
Any help is appreciated!
I think your app is trying to access datasource using deallocated instance, you better have individual classes for each tableView, it will simplify your work, always try to modulize the classes instead of trying to put everything in just one class.
I have a split view where the top section of my split shows some questions, and the bottom section shows some other stuff. The problem is that I had it written to "push" to a new view every time the user selects a question. This is obviously less than ideal because the user can enter a situation where they have 15 copies (more or less, depending on how many times the user selects a question) of the same question to go back through.
I thought that a simple solution would be to set a BOOL for when a user selects a question, but as it turns out, this introduces a new bug where the user can select a question once, but if they go back they are out of luck. I'm kind of stuck here, and any guidance would be greatly appreciated.
Program flow:
First you need to understand a little about what I am trying to do. I am building a historical inquiry app that focuses on allowing teachers to support student learning of historical inquiry. As such, there are core questions as well as documents the students can analyze.
Based on the way the app has come along, JSLDetailViewController displays the core questions and JSL_QuestionInteraction displays the questions for analyzing the documents.
Relevant code snippet:
-(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if(indexPath.section == 0){
if(!didSelectQuestion){
[self performSegueWithIdentifier:#"questionDisplaySegue" sender:indexPath];
didSelectQuestion = TRUE;
} else {
JSLDetailViewController *detailView = [JSLDetailViewController alloc];
detailView.telegram = indexPath.row;
[detailView setDetailItem:indexPath];
}
}else if(indexPath.section == 1){
[self performSegueWithIdentifier:#"telegramQuestionDisplaySegue" sender:indexPath];
JSL_QuestionInteraction *questionView = [[JSL_QuestionInteraction alloc] init];
questionView.managedObjectContext = self.managedObjectContext;
}
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if([segue.identifier isEqualToString:#"questionDisplaySegue"]){
JSLDetailViewController *detailView = (JSLDetailViewController *)segue.destinationViewController;
detailView.telegram = index.row;
[detailView setDetailItem:index];
JSLDetailViewController *controller = (JSLDetailViewController *)segue.destinationViewController;
controller.managedObjectContext = self.managedObjectContext;
} else if ([segue.identifier isEqualToString:#"telegramQuestionDisplaySegue"]){
JSL_QuestionInteraction *questionView = [[JSL_QuestionInteraction alloc] init];
questionView.managedObjectContext = self.managedObjectContext;
}
}
Please let me know if you need any additional details to understand this problem.
I don't know if any of what I write here will fix your problem because I still don't really understand your structure, but I see a couple of things wrong in your posted code.
First, when you're doing segues you shouldn't be alloc init'ing anything in code, the segue instantiates the new controllers for you. It's not clear what you're doing with detailView in didSelectRowAtIndexPath:, you do an alloc with no init -- you should never ever do an alloc without an init. If that detailView is something that's already present on screen, you should get a reference to that instance and set telegram and detailItem on that.
In the "if" clause of prepareForSegue you are assigning the segue.destinationViewController to two different local variables, detailView and controller -- they both point to the same thing, so there's no reason to have them both.
In the "else" clause, once again your alloc init'ing a controller, which you shouldn't do. You probably want to get the segue's destination view controller instead.