How do I insert UITextField in to UITableView after button is pressed - objective-c

In master view application xcode generates ready app with table view and the plus button. I want to change that button to to add a new cell but not with the date as it is by default. I want to add two text fields like label->textfield, label->textfield.
In code I have this:
- (void)viewDidLoad{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.navigationItem.leftBarButtonItem = self.editButtonItem;
UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(insertNewObject:)];
self.navigationItem.rightBarButtonItem = addButton;
self.detailViewController = (GCDetailViewController *) [[self.splitViewController.viewControllers lastObject] topViewController];
}
and the function:
- (void)insertNewObject:(id)sender{
if (!_objects) {
_objects = [[NSMutableArray alloc] init];
}
[_objects insertObject:[UITextField alloc] atIndex:0];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
Thank You

The way to think about this is model-view-controller (MVC). _objects is your model representing whatever the user thinks is in the table. Say it's a to-do list, then objects could be an array of NSObject subclass you create like TodoItem.
You would insert new TodoItems into _objects, then tell your table (the "View" in MVC) that it's model has changed. You can do that imprecisely using reloadData, or in a more targeted fashion as your code suggests, calling insertRowsAtIndexPaths - but that call must be sandwiched between tableView beginUpdates and endUpdates.
You can add textFields in code in your cellForRowAtIndexPath, or in the cell prototype in storyboard. Your table view datasource should always refer to objects... i.e. numberOfRows answers self.objects.count, cellForRowAtIndexPath gets:
TodoItem *item = [self.objects objectAtIndexPath:indexPath.row];
and uses that item's properties to initialize the textField's text. Also, incidentally, objects should be declared like this:
#property(strong,nonatomic) NSMutableArray *objects;
...and your code should refer to self.objects almost everywhere (not _objects). Initializing it on the first insert is too late, because the table needs it to be valid right-away, as soon as it's visible. Usually, a good practice is a "lazy" init replacing the synthesized getter...
- (NSMutableArray *)objects {
if (!_objects) { // this one of just a few places where you should refer directly to the _objects
_objects = [NSMutableArray array];
}
return _objects;
}

You might find using the free Sensible TableView framework really helpful here. Here is some sample code to illustrate how you'd do this using the framework:
- (void)insertNewObject:(id)sender{
SCTableViewSection *section = [self.tableViewModel sectionAtIndex:0];
[section addCell:[SCTextFieldCell cellWithText:#"Enter Text"]];
}
Comes in really handy for these types of situations.

Related

How to prepareForSegue in a UICollectionView depending on the contents of cell?

I have a UICollectionView with custom cells which display certain elements of a dictionary.
This is the dictionary;
-(instancetype)initWithIndex:(NSUInteger)index{
self = [super init];
if (self) {
EventsLibrary *eventsLibrary = [[EventsLibrary alloc]init];
NSArray *library = eventsLibrary.library;
NSDictionary *eventsDictionary = library[index];
_eventTitle = [eventsDictionary objectForKey:kTitle];
_eventLocation = [eventsDictionary objectForKey:kLocation];
_eventPrice = [eventsDictionary objectForKey:kPrice];
_eventDate = [eventsDictionary objectForKey:kDate];
_eventTime = [eventsDictionary objectForKey:kTime];
_eventDescription = [eventsDictionary objectForKey:kDescription];
_eventIcon = [UIImage imageNamed:[eventsDictionary objectForKey:kIcon]];
_eventIconLarge = [UIImage imageNamed:[eventsDictionary objectForKey:kLargeIcon]];
_eventType = [eventsDictionary objectForKey:kType];
}
return self;
}
The kVariables all have strings associated with them in an array labeled library.
I have been able to have the UICollectionView display what I needed on a specific view controller (eventTitle, eventLocation, eventPrice, eventIcon). I'm struggling now, in terms of having a segue which responds dependant on the cell selected. I have been successful in doing this for a UIImageView NSArray - yet I'm unfamiliar on how to work around this problem with a UICollectionView and a UICollectionViewCell.
I would like to perform a segue in order to further display (dependant on which cell was selected), some values from the dictionary which haven't yet been used (i.e. description).
I'm confused as to whether the ideal method would be written in the performSegue method or in the didSelectItemAtIndexPath??
Below is some additional code and screen shots for more information;
Story board of two view controllers with the segue identifier
Code for the initial viewcontroller with the UICollectionView on how the arrays are filled and the UICollectionView labels/images are associated
- (void)viewDidLoad {
[super viewDidLoad];
self.eventTitleArray = [[NSMutableArray alloc]initWithCapacity:8];
self.eventLocationArray = [[NSMutableArray alloc]initWithCapacity:8];
self.eventIconArray = [[NSMutableArray alloc]init];
self.eventPriceArray = [[NSMutableArray alloc]initWithCapacity:8];
self.eventTypeArray = [[NSMutableArray alloc]initWithCapacity:8];
self.eventDateArray = [[NSMutableArray alloc]initWithCapacity:10];
for (NSUInteger index = 0; (index < 8) ; index++){
EventsList *eventList = [[EventsList alloc] initWithIndex:index];
NSString *individualEventTitle = eventList.eventTitle;
NSString *individualEventLocation = eventList.eventLocation;
NSString *individualEventIcon = eventList.eventIcon;
NSString *individualEventPrice = eventList.eventPrice;
NSString *individualEventType = eventList.eventType;
NSArray *eventDate = eventList.eventDate;
[self.eventTitleArray addObject:individualEventTitle];
[self.eventLocationArray addObject:individualEventLocation];
[self.eventIconArray addObject:individualEventIcon];
[self.eventPriceArray addObject:individualEventPrice];
[self.eventTypeArray addObject:individualEventType];
[self.eventDateArray addObjectsFromArray:eventDate];
}
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
EventsCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"eventsCell" forIndexPath:indexPath];
cell.eventImage.image = [self.eventIconArray objectAtIndex:indexPath.row];
cell.eventTitle.text = [self.eventTitleArray objectAtIndex:indexPath.row];
cell.eventLocation.text = [self.eventLocationArray objectAtIndex:indexPath.row];
cell.eventPrice.text = [self.eventPriceArray objectAtIndex:indexPath.row];
cell.eventType.text = [self.eventTypeArray objectAtIndex:indexPath.row];
return cell;
}
I am able to currently click on the cells in the simulator, yet the second view controller has no content - as presumed due to there being no method in the segue
Hence I would appreciate some knowledge on how I would work around this with a UICollectionView?
prepareForSegue
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
EventView *collectionView = [[EventView alloc]init];
if([segue.identifier isEqual: #"showEventDetail"]){
if([self.yourEvents indexPathForCell:sender]){
NSIndexPath *index = [self.yourEvents indexPathForCell:sender];
collectionView.eventTitle.text = #"Hello";
collectionView.eventDescription.text = #"This Event";
}
}
}
The secondary viewController wont even update its properties collectionView.eventTitle.text to the hard string of #"Hello". Although when I NSLog the NSIndexPath *index - I get different values dependant on which cell I select. So I presume that the secondary VC is loading different instances dependant on the cell selected? Yet I'm more confused as to why the actual properties wont even change when assigned a hard string value?
I assume your actual question is whether the ideal method would be written in the performSegue method or in the didSelectItemAtIndexPath.
If this is the case, both can work.
You can try first didSelectItemAtIndexPath and pass the model object (in your case that could be Event) to the detail view controller. That could look like this:
-(void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath {
// Get the Event model representing the currently selected cell
Event *selectedEvent = self.events[indexPath.row];
// Create a new detail view controller with the selected event and push it
EventDetailViewController *eventDetailViewController = [[EventDetailViewController alloc] initWithEvent: selectedEvent];
[self.navigationController pushViewController:eventDetailViewController animated:YES];
}
If you want to use segues, your goal is again to get the selected indexPath. You can achieve that by using UICollectionView's indexPathForCell method as described in more detail in this answer.
Note that it might simplify your code a lot if you use a dedicated model class (the Event one I mentioned earlier) and then have an array of events as your datasource.

Multiple objects with NIB in NSMutableArray

I have a noob-question, that might be similar to Dynamically create multiple instances of a UIView from a NIB but I'm missing the big picture.
I've got a class with NIB, called UserViewController and I need to create multiple instances, based on the number of users, store them in an array and be able to modally navigate between them.
A class with NIB, called SelectNumberOfUsersViewController contains this IBACTION-code:
users = [[NSMutableArray alloc] init];
for (int i=0; i<numberOfUsers; i++) {
user = [[UserViewController alloc] init];
user.userid = i+1;
[user doInitialization];
[users addObject:user];
}
I see that the initWithNibName of the instance user is run, but how do I address and show the UI for the first user in the users array?
I'm not sure if commands like
myView = [[[NSBundle mainBundle] loadNibNamed:#"XXX" owner:self options:nil] objectAtIndex:0];
[[self view] addSubview:searchDateView]
should be used, since the array contains entire objects of the class User with NIB and everything - or...?
If you want to initialise a viewcontroller with a nib file you should use:
user = [[UserViewController alloc] initWithNibName:#"NibName" bundle:nil];
If you want to push that view you could call:
[self.navigationController pushViewController:user animated:true];
To present it, call:
[self presentModalViewController:user animated:true];
If you just want to add the view to the current viewcontroller use:
[self.view addSubView:user.view];
But the sure to remove the previous one too.
I hope this was of any help.

NSTableView WILL NOT RELOAD

Hey guys, so in my newest program I use an NSTableView to display words on the left, and thier definitions on the right. these words and definitions are load from a .plist file, and at application startup the table view loads these words and definitions and displays them just fine. My problem comes in when the user tries to add a word and definition using the text boxes and buttons, the word is actually added to the .plist, meaning the method is running fine, but the table view refuses to display the new line. only until after i quit the program and reopen it does the tableview display the new line. I tested to see if the table view was connected properly by sending it other messages such as selectedRow and dataSource, all came back with responces, and proper responces at that. Currently the class that is used as the dataSource and delegate is a subclass to my main class with all my varibles and dictionaries. (I am big on using as little classes as possible). Lastly I tried inserting noteNumberOfRowsChanged in before reloadData, but still nothing.
I have tested everything and it just seems that the reloadData method is not initiating anything. Like I said, my table view is being sent the message, the new info is actually being added to the dicitinoary adn array, the amount of rows is being updated by the count method, and what proves it even more is that when the program is restarted it displays everything just fine. below is the relevent code, where currentWordList and currentDefitionList are the Array and Dictionary suppying the data to the dataSource, and editLibraryCardList is the NSTableView I am trying to reload.
the dataSource class code:
#interface EditorDataTable : SAT_Vocab_MacController {
IBOutlet NSTableColumn *editLibraryWordColumn;
IBOutlet NSTableColumn *editLibraryDefinitionColumn;
}
- (int)numberOfRowsInTableView:(NSTableView *)tableView;
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row;
#end
#implementation EditorDataTable
- (int)numberOfRowsInTableView:(NSTableView *)tableView {
return ([currentWordList count]);
}
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row {
if (tableColumn == editLibraryWordColumn) {
return [currentWordList objectAtIndex:row];
}
if (tableColumn == editLibraryDefinitionColumn) {
return [currentDefinitionList valueForKey:[[currentWordList objectAtIndex:row]lowercaseString]];
}
}
#end
method that adds the word to the list:
- (IBAction) editLibraryAddWordToLibrary: (id) sender {
if (self = [super init]) {
currentWordList = [[NSArray alloc] initWithContentsOfFile:userSATWordListPath];
currentDefinitionList = [[NSDictionary alloc] initWithContentsOfFile:userSATDefinitionListPath];
}
[currentWordList addObject:[[editLibraryNewCardWordInput stringValue]capitalizedString]];
[currentDefinitionList setObject:[editLibraryNewCardDefinitionInput stringValue] forKey:[[editLibraryNewCardWordInput stringValue]lowercaseString]];
aWordCounter = [currentWordList indexOfObject:[[editLibraryNewCardWordInput stringValue]capitalizedString]];
[aWordLabel setStringValue: [[NSString alloc] initWithFormat:#"%#", [currentWordList objectAtIndex: aWordCounter]]];
[aDefinitionLabel setStringValue: [[NSString alloc] initWithFormat:#""]];
[currentWordList writeToFile:userSATWordListPath atomically:YES];
[currentDefinitionList writeToFile:userSATDefinitionListPath atomically:YES];
[cardCountdownNumber setStringValue: [[NSString alloc] initWithFormat:#"%i", ([currentWordList count] - (1 + aWordCounter))]];
[editLibraryCardList noteNumberOfRowsChanged];
[editLibraryCardList reloadData];
}
Iv'e been stuck for days and any ideas will help! Thanks.
Zach
Have you tried debugging into your selectRowAtIndexPath method to make sure the reload occurs? (after you call [tableView reloadData] should be able to see this) Are you using UITableViewController?
If you wanted a callback after reload to know when its done, you could try:
[tableView reloadData];
[self performSelector:#selector(selectRowAtIndexPath:) withObject:indexPath afterDelay:0.0];
For those who are curious, i moved my code from the dataSource subclass to the main class, and it worked. i guess you cannot subclass the dataSource. Hope this helps!

toolbarSelectableItemIdentifiers: is not called

I'm trying to make selectable NSToolbarItems. I've connected everything correctly in IB, but toolbarSelectableItemIdentifiers: is not working. It doesn't get called. The delegate is the File's Owner (subclass of NSWindowController), and the toolbar is in a sheet. Here's my code:
// TOOLBAR DLGT
- (NSArray *)toolbarSelectableItemIdentifiers:(NSToolbar *)toolbar {
NSLog(#"Foo");
NSMutableArray *arr = [[NSMutableArray alloc] init];
for (NSToolbarItem *item in [toolbar items]) {
[arr addObject:[item itemIdentifier]];
}
return [arr autorelease];
}
Screenshot:
Can you help me please?
No, I don't want to use BWToolkit.
Are you positive the toolbar's delegate outlet points to the class (or instance thereof) you think it does? Are any other NSToolbar delegate methods called there (easy enough to test)?

Passing variables (or similar) to newly loaded view

I am loading new views for a small iphone app, and was wondering how to pass details from one to another?
I am loading a tableview full of data from and xml file, then once clicked a new view is brought in via:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
SubInfoViewController *subcontroller = [[SubInfoViewController alloc] initWithNibName:#"SubInfoView" bundle:nil];
[self presentModalViewController:subcontroller animated:YES];
[subcontroller release];
}
Next step would be to tell the newly loaded view which row had just been loaded?
Any idea, thoughts more than welcome, and please be gentle big newbie...
I typically create my own init method to do things like this. I think it would likely be better to pass in the corresponding "model" object represented by the tableView row, rather than the row number itself, like this:
In SubInfoViewController.h
#interface SubInfoViewController : UIViewController {
YourObject *yourObject;
}
#property (nonatomic, retain) YourObject *yourObject;
Then in SubInfoViewController.m:
- (SubInfoViewController*)initWithYourObject:(YourObject*)anObject {
if((self = [super initWithNibName#"SubInfoView" bundle:nil])) {
self.yourObject = anObject;
}
return self;
}
You'd create and present it this way:
// assuming you've got an array storing objects represented
// in the tableView called objectArray
SubInfoViewController *vc = [[SubInfoViewController alloc] initWithYourObject:[objectArray objectAtIndex:indexPath.row]];
[self presentModalViewController:vc animated:YES];
[vc release];
This could be adapted pretty easily to allow you to pass in any type of object or value (such as a row number if you still want to do that).
Add an instance variable to your view controller and declare a property corresponding to it, so after you alloc, init it, set it like subcontroller.foo = Blah Blah.