I asked a related question that I realized was way over-complicated, so I made some design decicions and have simplified what I need to do here. I am using Xcode 5 and designing for iOS 7.
I have a navigation controller, a main menu (menuViewController) with a button, and a calculator (calculatorViewController) that is arrived at when the button on the main menu is clicked.
My Xcode storyboard is set up like this and it works perfectly (not an image, but I think this should be clear):
navigation controller -----> menuViewController ------> calculatorViewController
I would like to set up my main menu to have 3 buttons, each going to one of the following:
2 separate independent calculatorViewController units
1 other unrelated view
Because of the additional unrelated view, and because I don't think it makes sense for a few other design and content reasons, I do not want to use a UITable Master / Detail setup.
My question is how I might create a "duplicate" of my view and its code that can link to a second button on the main menu and run independently of the first. I know how to link the button from the main menu to the second instance of the view, but not how to create the second instance in the first place.
Of course, I want to conserve on memory, amount of code, and use best practices.
Any help would be greatly appreciated. Thanks in advance!
EDIT: It looks like this is unclear. I'm trying to figure out how to make it moreso.
Menu Buttons
button 1 ----> goes to calculator 1
button 2 ----> goes to calculator 2 (a second, identical, independent version of calculator 1)
button 3 ----> goes to another unrelated view
I know how to make the buttons get to the views. I am trying to figure out what the best way is to create the second calculator. Also, I want to make it clear that both of these calculators need to be able to run at the same time, and so both need to stay on the stack.
Thanks.
EDIT: Thanks for bearing with me.
Here is my code declaring the properties in my destination VC .h file:
// Properties for segue identifiers
#property(nonatomic, readonly) NSString *tankCalcOne;
#property(nonatomic, readonly) NSString *tankCalcTwo;
Here is the code in the .m file for my menu / source VC:
// This allows the view for tankCalcOne or Two depending on which button is clicked in the menu
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"tankCalcOne"])
{
[[segue destinationViewController] TankCalculatorViewController:self];
}
else ([segue.identifier isEqualToString:#"tankCalcTwo"]);
{
[[segue destinationViewController] TankCalculatorViewController:self];
}
}
I am getting 'No known instance instance method for selector TankCalculatorViewController' errors in both halves of my if/else statement. I can see why, since I haven't declared them, although I am not sure where I should declare them, as TankCalculatorViewController is the name of the view itself.
I don't think you need a second instance. You do not have to use the push segue. If you use the push segue, it will automatically carry the navigationController. Use another segue or a custom one..
I'll try explain how I would do this.
Create all needed segues and views/scenes in your storyboard (only one calculator scene with two segues leading to it)
Set different identifiers to your segues. This is important.
Create source code for your viewcontrollers and assign them to your storyboard's scenes (again only one calculator view controller with a property for you to specify mode)
In your menuViewController setup your - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender method. Check segue.identifier in order to know which segue exactly is this (you set those in step 2). There in segue.destinationViewController variable you will get already created your target view controller so you can set calculatorViewController.useSecondVariant or whatever property you choose to use in order for your calculator to know its working mode.
Related
As someone who usually used separate xibs in the past I thought I'd give storyboard a go as it seemed a lot simpler to use and much easier to develop with. I've been writing an application where the essential set up is this:
At the top of all this is a UINavigationController (first level). Then I have Multiple UIViewControllers (second level) with buttons in them which you can tap to switch between the second level UIViewControllers.
However a problem occurs when I start switching between the second level UIViewControllers. I first thought this was an initialisation problem with the NSMutableArrays because in my code I have a NSTimer set to loop periodically and found when I set a breakpoint during it, when I went forward to the next timer tick event there appeared to be different instances of the same NSMutableArrays and it seemed a gamble to try and insert new values into these array with it sometimes working, sometimes not (as it may or may not insert into the correct instance).
Then, looking at the memory usage under Debug Navigator I found the issue. Each time I "switched" between the UIViewControllers a new UIViewController was being initiated, along with all new variables.
The code I am using to switch between them is
-(void) perform {
[[[self sourceViewController] navigationController] pushViewController:[self destinationViewController] animated:NO];
}
Or essentially a push segue transition. This also explains why when I tried to switch back to my view, the data on that view was lost as it is a complete new view.
Does anyone know how to switch between multiple ones of these UIViewControllers (say 5) essentially like a UITabViewController would except without the tab bar being present?
First option you can do this: You can use a tabbarcontroller for switching viewcontroller and hidden the tabbar. Then on buttonclick setthe tabbar index.
Second option you can do this: Create one more view controller and in this viewcontroller subview the all switching viewController and when you want to switch viewcontroller just bring that viewcontroller view to front by delegate.
Do you need the navigation bar and other features provided by your top level navigation controller?
If not, you could use a UIPageViewController instead.
You set up all your second level view controllers and then just have to tell the page view controller which one to display.
If you implement the associated delegate methods, it will automatically provide swipe gestures to switch between them and nice animations to get them on and off screen.
You can also get it to put a UIPageControl at the bottom showing a dot for each VC with the dot for the current VC highlighted.
This question already has answers here:
Passing data between view controllers
(45 answers)
Passing data from popped view controller
(2 answers)
Closed 9 years ago.
I'm developing my first iOS app and I've been trying to adhere for good MVC practices and laid out conventions to make me app easy to maintain and redevelop. I'm making use of a Storyboard and largely I will use Push segues to navigate through my app. Here is a snapshot of my Storyboard.
When Add Staff is clicked, a push segue moves to Select Staff. Once on Select Staff, I select the staff I want and can return to the previous VC. I should mention that each VC has it's own .m and .h which I subclassed from UIViewController. Here is what it looks like when used.
Now I've hit a brick wall in transferring the selected data rows back to the first VC. My problem is that I don't know how to best transfer data back to a VC from one which has been pushed by it. In the Select Staff VC, I have an array (currentSelected) which correctly holds the Strings of the selected rows, but I want to access this array in the Create VC once I have rolled back.
How do I best go about doing this?
P.S. It's worth noting that I realise there are similar questions to this one, but they are either for slightly varying issues, slightly complex or folder older versions of XCode which talk about XIBs and stuff. For the past while I've been researching the literature on the issue and can't make effective headway, so I hope this explains why I made this question.
You can use block or delegate. I personally recommended a block because you keep your code grouped logically. With block you have to do something like that. In your SelectStaff.h file add typedef before interface:
// I use NSString as an example but for your project more accurate would be NSArray
typedef void (^ReturnBlock)(NSString *arg);
After that in #interface but before #end add Property:
#property(copy) ReturnBlock returnBlock;
In SelectStaff.m when you have your data (maybe in your save: method or dismiss:)
Call the block with the data you want to pass;
if (returnBlock)
returnBlock(#"Data will be passed do previous view controller");
In your CreateViewController.m in prepareForSegue: add block:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"YOUR_IDENT_FROM_STORYBOARD"])
{
SelectStaff *selStuff = (SelectStaff*)[segue destinationViewController];
[selStuff setReturnBlock:^(NSString *arg){
// This are your data
NSLog(#"Arg: %#", arg);
}];
}
}
Hope this help.
So, I followed this tutorial: http://enroyed.com/ios/how-to-pass-data-between-ios-tab-bars-using-delegate/
And the most important part of the tutorial:
- (void)viewDidLoad
{
[super viewDidLoad];
SecondViewController *svc = [self.tabBarController.viewControllers objectAtIndex:1];
svc.delegate = self; //important !! otherwise delegation will not work !
}
The problem is that even if I put it in "viewWillLoad", it still forces me to click on my tab before it initializes. How can I specify this before I click on the tab?
Edit
I have a three tab project. I used that tutorial in the link pass data from tab 1 to tab 2. The data passed is a url from a webview on tab 1 to a url on tab 2. The url gets pass when I click a link on the 1st tab.
The data does get passed, but only if I physically click on the 2nd tab first and then click back to the 1st and click on the link.
So, it appears to me that my code above only runs if I physically click on the 2nd tab.
Your problem is that - until you actually go to tab item 2, secondViewController is not fully initialised, and so there is no data to transfer from vc2 to vc1. In particular, secondViewController's view has not yet loaded, so there is no value to be had from it's slider yet, and no slider, so also no IBAction method to call to trigger the delegate method. Indeed, as the data transfer is only triggered on moving the slider in VC2, it should be fairly obvious that until you go to vc2 and move the slider, nothing is going to happen.
The example you link to uses the delegation pattern, which seems a fairly poor way to deal with your problem. The delegation pattern most comfortably fits with the scenario where there is a hierarchical relationship between delegator (owned) and delegatee (owner) ... not always, but most commonly. In a tab bar controller, the relationships are more like kindred child relationships to the tab bar controller itself.
You haven't offered enough detail in your question as to what you want to achieve, but you need to consider this:
When a tabBarController loads, all of it's child viewControllers are initialised but their views are not loaded.
This means that these methods do get called:
//if loading from a xib or in code
- (id) initWithNibName:bundle
//if loading from a storyboard
- (id) initWithCoder:
- (void) awakeFromNib
But the view loading methods (viewDidLoad, viewWillAppear etc) do not get called as the view does not get loaded unless you actually open the relevant tab.
You could solve this by putting an initialised variable into your viewController2 (in one of the init methods) and accessing that variable via property syntax from vc1. But then, you might as well just put an initialised value directly into vc1. You need to think closely about how each vc is dependent on the other, how you can decouple that dependency, and perhaps how to set up an independent data source that both vcs can access as needed. This could be a model class, or NSUserDefaults, or a property in your appDelegate... just a few of the many possible solutions.
After struggling for days on firing a segue conditionally, I managed to solve it thanks to Simon's answer here. Please take a moment to have a look or you might not understand what I'm talking about below. I didn't copy paste his answer because he's already explained it nicely over there.
Now I've faced a new question. What if I have multiple View Controllers that I want to segue to from one View Controller?
To explain it further : Say I have one MainViewController with 2 buttons. When clicked upon each button, it should segue to their respective View Controller. First button to FirstViewController and the second button to SecondViewController.
The method described in Simon's answer can be used when you segue from one View Controller to another View Controller. Since in that method, you tie the segue to the View Controller itsrlf and not to the button, you have only one segue with an identifier for that particular View Controller. Therefore I cannot distinguish between the button taps separately.
Is there a workaround to solve this problem?
Thank you.
It might be bit premature to say this but I guess you should look into Segue more deeply.
Yes you can perform segure from button. Just control click the button and drag the cursor to view controller you want it SEGUE'd. And from my understanding only condition there is each button tap results a segue to a fixed view. There is no condition there.
Also, you can push the navigation controller manually by
YourViewController *destViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"YourDestinationViewId"];
[self.navigationController pushViewController:destViewController animated:YES];
UPDATE:
prepareForSegue is too late to stop a segue from proceeding. Yes you can create multiple segues from your view to other view controllers. And in this case you have to do so. Don't reate a segue from button, just define a IBACtion on the button click you can do the validation from there,
if(validationSuccess) {
[self performSegueWithIdentifier:#"segue1" sender:self];
}
if you are using ios6
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender
return YES on validation success and NO on failure to stop it from proceeding.
I suggest you look a bit at reworking your code logic.
If I understand correctly, you have a VC (embedded in a Nav. Controller) with 2 buttons and you have figured out how to segue each button to a different VC.
Your problem is you want to make sure that even if one of the buttons are pressed, a validation is done before an action takes place. I would advise this is bad User Interface design because the user has the illusion that this button might do something and then they click it and nothing happens.
UIButton can be connected to IBActions (to initiate actions) and IBOutlets (to set their properties). If this is a button created in IB directly, I would connect it to your class as an Outlet property:
#property (nonatomic,weak) IBOutlet UIButton* myButton;
And then set its enabled value:
self.myButton.enabled=NO;
This will keep the button and dim it. This is much better UI design and the user knows they should not press the button because some condition is not satisfied.
I would rework the code so that you set this value as disabled by default for example and enable it appropriately in your code whenever your "condition" is satisfied.
Obviously if this button is created programmatically (in your code without IB) then it is easy to just use the second command above.
Hope this helps.
I just wrote another way to call multiple detail views from a single table. Each cell could essentially make a different view be displayed. The code is similar to what you see in this post but you essentially use identifiers and attributes on the list item to determine which view to show.
https://codebylarry.com/2016/07/15/multiple-detail-views-in-swift/
override func tableView(tableView: UITableView,didSelectRowAtIndexPath indexPath: NSIndexPath) {
if indexPath.row == 1 {
self.performSegueWithIdentifier("secondView", sender: self)
} else {
self.performSegueWithIdentifier(“others", sender: self)
}
}
The kind of design that I'm trying to accomplish in my app currently consists of a navigation controller that is the root controller for another View controller known as Test. Test consists of a UITableView in the top half and an UIImageView in the bottom half. THe navigation bar on the top (which is there as a result of the navigation controller) contains two buttons. The image at the link below ( I don't have the reputation points to post an image directly) should make it very clear.
http://i.imgur.com/GM5eH.png?1
I want my design to be in such a way that depending on which button is pressed the image in the imageview is changed while, though the the text in the table view remains the same, they will transition to completely different screens going forward. To give an example, regardless of which button is tapped my table view will consist of : Option 1, Option 2 and Option 3. However, Option 1 for button A is different from Option B and so on.And, this is where the challenge is for me. I have been able to swap out the images based on the pressing of the button succefully. I did this by using an IBActionNavBarButonPressed and then swapping out the images based on the sender tag. Unfortunately, I don't know how to proceed from here. So for example I have my next couple of screens here. But, how do I set up the segues/transitions in such a way that only Option 1 of choice A goes to a certain screen and so on. From my understanding, I'm looking at a combination of prepareforSegue and the navBarbuttonPress IBAction but I'm still not sure how this would work.
Guys, I'm fairly certain as to what I'm trying to but since I'm new to objective C, I'm not completely sure of how to do it. Essentially, I want the logic to be something like, if(element.selected==0) && (IBActionProvider==1) { performSegueWithIdentifier:#"blah" sender.self]; My issue is whether to put this in the IBAction navBarButtonPressedMethod or to put it in the didSelectRowAtIndexPath method.
Thanks and sorry for the long question!
First you give names to other storyboards views. It contains left hand panel(Storyboard Identifier :).
You should create segue for all buttons and give identifier to it.
Then you should use prepareforSegue like this
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"ToContacts"])
{
// Get reference to the destination view controller
ContactVC *targetVC = [segue destinationViewController];
// Pass any objects to the view controller here, like...
targetVC.CompanyNme = selectedRowValue;
}
if ([[segue identifier] isEqualToString:#"ViewCompany"])
{
ContactVC *targetVC = [segue destinationViewController];
targetVC.CompanyNme = selectedRowValue;
}
}
#"ToContacts" means your storyboard identifier.