I am currently working on a project that requires a list of customers to be displayed in a UITableView, the associated cell then segues to a TabView to display a detailed customer record in a tabbed ui.
I have setup the story board with the required TableView and populated fine. The TabViews all setup and I have added a custom class to the main TabView controller which can take the ID (required to interrogate service and return further data) and Customer Name. I have also added a UIViewController for the first tab in which I need to get the ID value.
I can't seem to get hold of the ID or Company Name that is passed. I have tried importing the .h file of the UITabView. I know the UITabView .h file is being populated with the values as in the .m file I am using the Customer Name to update the title of the Navigation Bar. However, whenever I breakpoint on line that gets the ID in the .m file for the individual tab, it always returns nil.
I am using the following code to try and get this value:
companyTabController *headerData = [companyTabController alloc];
_companyName_lbl.text = headerData.companyName;
_companyID_lbl.text = headerData.ID;
I have tried several variations of the above and all to no avail.
You can also use NSUserDefaults to save the data, I think that is the simplest way to save the data throughout the app.
From the code you posted, the headerData is a new instance. So the companyName and the ID will be nil unless you assign some value to them.
Since, you mentioned that you are able update the navigation bar title, try using the same object for fetching the values in this controller as well. (Maybe you can use a singleton object)
If your segueing you have to use the prepareForSegue:sender: method as such:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
companyTabController *companyTC = [segue destinationViewController];
companyTC.companyName_lbl.text = headerData.companyName;
etc
}
if your not segueing you will have to instantiate it as such :
- (void) didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *selectedCell = [self.tableView cellForRowAtIndexPath:indexPath];
companyTabController *companyTC = [self.storyboard instantiateViewControllerWithIdentifier:#"CopmanyTabController"];
companyTC.companyName_lbl.text = selectedCell.textLabel.text or = headerData.companyName;
[self.navigationController pushViewController:companyTC animated:YES];
}
Related
I am rebuilding an application where I need to show a list of items. This list is retrieved using an API or retrieved from Core Data and shown in a UITableViewController. The problem I am having is that there are already seven different lists where there are small differences. Mostly of them are just different items, but also a search bar included in one list and previously stated one list will not load using an API, but from Core Data.
In my Storyboard I have added a UITableViewController with the class ItemsTableViewController which has a designed UITableViewCell. I have added an identifier to this cell so I can reuse it inside this view controller. There is a segue to this view controller from the home screen.
The idea was to create one parent object (ItemsTableViewController) and add multiple child objects (SavedItemsTableViewController, LocalItemsTableViewController, etc.) which will all use the shared logic of the parent with only some small changes (API and some custom things).
What I currently have is working, but without the child objects:
- (void)offlineButtonPressed {
[self performSegueWithIdentifier:#"openItemsTableViewController" sender:#(ItemListOffline)];
//[[self navigationController] pushViewController:[[OfflineItemsTableViewController alloc] init] animated:YES];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:#"openItemsTableViewController"]) {
switch((ItemList)[sender unsignedIntegerValue]) {
case ItemListOffline: { [(ItemsTableViewController *)[segue destinationViewController] retrieveOfflineDocuments]; break; }
case ItemListSearch: {
[(ItemsTableViewController *)[segue destinationViewController] retrieveDocumentsWithQuery:#""];
[(ItemsTableViewController *)[segue destinationViewController] addSearchBar];
break;
}
// Loop through all the list...
default: { NSAssert(NO, #"Unhandled type of document list."); break; }
}
}
}
So the application is calling just a function of the parent object which handles the request. But as you probably can feel, the object will be less maintainable. In comments I pushed the child view controller, but since it doesn't include the storyboard view, I need to recreate the cell from scratch, which doesn't feel good. Plus since the reuse identifier, it is not working since it is unable to dequeue a cell with the identifier set in the tableView:cellForRowAtIndexPath: method.
Now I am wondering what would be the best practice for this? It seems I can't use the save UITableViewController from the storyboard for multiple child classes, can I? But creating seven (and probably a even more in the future) controllers in the storyboard where I need to copy the cells to each controller and just give them different classes doesn't seem the way to do it. And add a method to the parent where the list would be retrieved differently and changing some stuff like adding a search bar is working, but also not the nicest way it seems.
Update: The logic in the ItemsTableViewController is pretty simple. In the delegate and datasource I handle the documents almost the same. The method that does the retrieving per list type is something like:
- (void)retrieveOfflineItems {
[self startLoading];
[[APIManager instance] getOfflineItems:^(NSArray<ItemList *> *list, NSError *error) {
[self setDocuments:list];
[[self tableView] reloadData];
}];
}
But there are more things, like the search has to add a search bar (once the view is loaded). So it needs to call multiple methods when performing the segue.
You can have a single UITableViewController, which includes all of your possible table cells, and as part of the prepareForSegue call, you should set an type identifier for the controller and the data - no matter where the data came from.
Within the UITableViewController class, you can hide / display the features that you need for this data type - such as the search bar, and in the tableView methods, choose which data source you need.
This way, the one UITableViewController class that you need to maintain is a little more complex than one dedicated class, but a lot more maintainable than 7 or more!
Assuming you have set up a variable dataType to identify the type of data you need, you could have something like this for the numberOfRowsInSection, and then similar for the other tableView methods
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
switch self.dataType {
case dataType.MyFirstDataType:
return myFirstDataTypeArray.count
case dataType.MySecondDataType:
return mySecondDataTypeArray.count
case dataType.TheCoreDataType:
return myCoreDataArray.count
default:
break
}
}
Create a parent UITableViewController with all subviews and cells including searchController. Initialize it and add/remove views and cells based on conditions
I'm having trouble transferring data between a table view controller and another view controller.
On my sending controller - tableviewcontroller.m - I have this under prepareForSegue:
FriendDetailViewController *fvc = (FriendDetailViewController *)segue.destinationViewController;
NSIndexPath *path = [self.tableView indexPathForSelectedRow];
PFUser *u = self.friends[path.row];
fvc.nameLabel.text = u.username;
//friendViewController.m (the destination view controller)
- (void)viewWillLoad {
[super viewDidLoad];
if (self.user) {
self.nameLabel.text = self.user.username;
}else{
self.nameLabel.text = #"No data to display";
}
}
The label displays "No data to display".
I've read other posts and viewed tutorials but I cannot figure this out.
You're only setting the nameLabel in your prepareForSegue, not the actual user, so it is uninstantiated when your new VC loads.
Try adding this into your prepareForSegue:
fvc.user = u;
Assuming your user you want to pass is stored in u, that should do it (also assuming user is a public property of the new view controller.)
You can put an attribute in your .h file of FriendDetailViewController and the set it before calling:
fvc.attribute = u.username;
After this, you can call pushViewController for "fvc" inside didSelectRowAtIndexPath
You can't set the UI actions on prepareForSegue.
You could instead define the user variable on destination viewcontroller.
After that, on prepareForSegue, you can set the selected PFUser to the destination viewcontroller's user.
And when you'd like to show the user's name, use viewWillAppear instead of viewWillLoad.
If you can't understand, please let me know, I can show detailed code for all.
I am trying to learn iOS development but have stalled a bit so I hope that there is some kind soul here who might be able to help me in the right direction.
Let's say I have a UITableViewController that displays a number of items, consisting of a title and subtitle ( Subtitle style of a Tableview Cell). Items.m/h only consist of two properties, title and subtitle and a init method to set the properties. In my app delegate i create some default items and pass them/set them to my tableViewController's property tvc.items, which is a NSMutableArray. What do I need to do / what components do I need, to be able to add more items and then display them in my tableViewController?
I started with the following:
Added a new view controller in the storyboard
Embeddade the viewController in a Navigation Controller
Added a Bar Button Item at my Table View Controller with an identifier of add
Ctrl + drag from BarButtonItem (add) to my new view controller selected modal segue
Created a new class AddNewItemViewController
Entered this as the class under the Identity Inspector for the new view controller
I then added two Bar Button Items, Cancel and Done (with cancel and done as identifiers) in the storyboard for the new View Controller
This was followed by me adding two UITextFields, one for the Title and one for the Subtitle
Ctrl + drag from these outlets into AddNewItemViewController.m, between #interface AddNewItemViewController () ... here ...#end (so they become Private? Should I drag it here or to AddNewItemViewController.h ?, What is the standard way for doing similar outlets?).
In AddNewItemViewController I added two properties, NSString's (nonatomic, copy) * title and *subtitle which I thought would keep the input data from an intended user.
So, after this I now want do two things, and it is here as it becomes difficult (for me at least):
Making so that by clicking on Cancel, one return to the Table View controller, ie a dismissed the modal .
Adding the data within the text fields to that NSMutableArray which is the datasource by clicking Done.
So what is required of me to do the last two steps?
Where should I ctrl + drag from the Cancel and Done (so there will be actions)? I guess they must be submitted to AddNewItemViewController.m, but what must be done to dismiss the modal (by clicking on the 'Cancel') and what should be called at or performed when clicking on 'Done'?
Which or what class (es) must know about the other class?
Last but not least, what should I send in the prepareForSegue call (which I guess I will need to have to use to send the input data back to the table view controller)?
Where to start and what methods should i learn about in order to achieve my mission?
Best Regards,
Rookie
much quesetions :)
I will beginn with the close action.
Have a look at the AppleDocumentation, dismissViewController with sender self (your AddViewController).
To store your data from AddViewController to your TableViewController, it's a better way to use delegation.
AddViewController.h
#protocol AddViewControllerDelegate;
#interface AddViewController : UIViewController
#property (nonatomic, weak) id<AddViewControllerDelegate>delegate;
#end
#protocol AddViewControllerDelegate <NSObject>
- (void) addViewControllerDidFinishTakingData:(AddViewController *)addViewController withTitle:(NSString *)title andSubtitle:(NSString *)subTitle;
#end
AddViewController.m
- (IBAction)done:(id)sender
{
NSString *title = ...;
NSString *subtitle = .. .;
[self.delegate addViewControllerDidFinishTakingData:self withTitle:title andSubtitle:subtitle];
}
TableViewController.m
#interface TableViewController ()<AddViewControllerDelegate>
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"yourIdentifier"])
{
AddViewController *addViewController = (AddViewController *)segue.destinationViewController;
addViewController.delegate = self;
}
}
Last but not least to implement your new delegate-method
- (void)addViewControllerDidFinishTakingData:(AddViewController *)addViewController withTitle:(NSString *)title andSubtitle:(NSString *)subTitle
{
// handle your data here (store to array)
// reload your table
}
Better way, to create a Class (Model) for every entry.
The simplest thing to do would be to assign tvc.items to the destinationViewController's property during prepareForSegue.
You are correct in thinking that the Cancel and Done buttons belong to the AddNewItemViewController.
In the action for Done, you could add the new item to the items array you passed in during prepareForSegue, then in the presenting view controller (the one you launched the modal from), during viewDidAppear just reload the table. It'll be called when the modal disappears.
In ViewContrller1.h :
#property (retain,nonatomic) NSString *myString;
In ViewController2.m , I want to read a value from a text box and assign it to viewContrller1.theString and go to ViewContrller1
I used this method but I get a null value of my string in ViewContrller1:
- (IBAction)buttonPressed:(id)sender {
ViewContrller1 *go=[self.storyboard instantiateViewControllerWithIdentifier:#"ViewContrller1"];
go.myString=self.myTextFeild.text;
[self.navigationController pushViewController:go animated:YES];
}
Wondering how it suppose to work?
try to simple import viewcontrller1.h in the new view controller.
#import "viewcontrller1.h"
Marcal's answer should also work.
To pass properties between 2 views, the recieving view must have a public property. For example, your VC 2 shoud have a public NSString. This means declared on your .h file. Then on your initial VC you have to pass the string on the prepareForSegue method. Something like that here:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"vc2segue"]) {
UIViewController *vc=segue.destinationViewController;
vc.publicNstring=self.someTextView.text;
}
}
There is nothing wrong with the code that you've posted, assuming you've made no typos and have connected the text field and the button to your second view controller (the one sending the string) and that you've added the Storyboard ID to the first view controller within your storyboard - see here http://sketchytech.blogspot.co.uk/2012/11/instantiate-view-controller-using.html
Assuming you've also embedded the view controller within a Navigation Controller, then a line of code in viewcontrller1.m (viewDidLoad:):
NSLog(#"%#",self.myString);
Should confirm it works. In fact, I just tested it with your code and it works. Check for typos would be my advice if you've done all of the above.
I'm creating an app using storyboards where a first screen has a list of songs in a table view and I want to pass that song to the next view (let's call it the music player). I can accomplish this, but I also need to music player to keep track of various exercise statistics while the music player is playing music.
The problem is, if I enter the music player, go back to the song list view, and then select a cell again, it creates an entirely new instance of the music player. This is problematic as it creates a new timer and I lose all my exercise statistics. I also discovered that the original music player instance still exists because the timer continues to fire.
Is there anything I can do to make sure only one instance of the music player continues to appear?
Here is my current code (in order of execution):
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
self.nextScreen = [segue destinationViewController];
}
^ This stores the next screen as a member variable. I have to do this because prepareForSeque: is called before didSelectRowAtIndexPath: and I need to set data on the next view.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
self.nextScreen.currentSong = [self.allSongs objectAtIndex:indexPath.row];
}
^This just sets the song.
I then proceed to set the next view up as needed in viewDidLoad:.
Is there a trick to fix this? If not, any suggestions how I get around this? I'm thinking one workaround is I'll have to set up some sort of singleton, I'm just not sure if that's the best option since I will need to reset the data so often.
Thanks!
You can't do this with segues. Segues (with the exception of unwind segues) always create new instances when they are performed. You can push to your new controller (assuming your embedded in a navigation controller), and in the method where you do so, only create a new instance if it's the first time.
-(IBAction)goToNext {
if (! self.next){
self.next = [[NextViewController alloc] init];
self.next.whateverProperty = whatever you want to pass;
self.next.delegate = self; // set the delegate here if you're using delegation to send info back to this controller
}
[self.navigationController pushViewController:next animated:YES];
}
You can still set up your controllers in the storyboard if you want, just don't connect them together. In that case you would instantiate them with the following instead of using alloc init:
self.next = [self.storyboard instantiateViewControllerWithIdentifier:#"next"];