Is there a way to change what controller the segue is going to invoke in prepare for segue? I'm trying to do that when a segmentedcontrol is changed with an embedded segue. Thanks!
As you may have noticed the segue's destinationViewController is readonly. Your better strategy would be to have segues defined between the view controller that holds the segmented control (not a view or control) and the other view controllers that you want to choose among. Make your decision based on the selected segment and call performSegueWithIdentifier:sender: from the controller's code with the identifier that matches the segment.
If you want to switch which controller is the embedded controller, then I think you need to use the custom container view controller paradigm that Apple uses. The code I have below is from a small test app that does that. This was set up using the single controller template, and then adding a container view to that controller (called ViewController), and a segmented control to the main view. I then added a disconnected view controller, changed its size to free form, and then adjusted its view size to be the same as the embedded controller's view size. Here is the code in ViewController.h:
#interface ViewController : UIViewController
#property (weak,nonatomic) IBOutlet UIView *container;
#property (strong,nonatomic) UIViewController *initialVC;
#property (strong,nonatomic) UIViewController *substituteVC;
#property (strong,nonatomic) UIViewController *currentVC;
#end
And this is what I have in the ViewController.m:
- (void)viewDidLoad {
[super viewDidLoad];
self.initialVC = self.childViewControllers.lastObject;
self.substituteVC = [self.storyboard instantiateViewControllerWithIdentifier:#"Substitute"];
self.currentVC = self.initialVC;
}
-(IBAction)SwitchControllers:(UISegmentedControl *)sender {
switch (sender.selectedSegmentIndex) {
case 0:
if (self.currentVC == self.substituteVC) {
[self addChildViewController:self.initialVC];
self.initialVC.view.frame = self.container.bounds;
[self moveToNewController:self.initialVC];
}
break;
case 1:
if (self.currentVC == self.initialVC) {
[self addChildViewController:self.substituteVC];
self.substituteVC.view.frame = self.container.bounds;
[self moveToNewController:self.substituteVC];
}
break;
default:
break;
}
}
-(void)moveToNewController:(UIViewController *) newController {
[self.currentVC willMoveToParentViewController:nil];
[self transitionFromViewController:self.currentVC toViewController:newController duration:.6 options:UIViewAnimationOptionTransitionFlipFromLeft animations:^{}
completion:^(BOOL finished) {
[self.currentVC removeFromParentViewController];
[newController didMoveToParentViewController:self];
self.currentVC = newController;
}];
}
Related
I am trying to implement a flip between two view controllers embedded into a container view. In the real app, the flip button swaps between a map and a list.
In this mockup of the problem, the Coffee UITableViewController is swapped out for the Sweets UIViewController.
It works fine in the simple case, but when I add a segue to the Sweets Controller to go to a child UIViewController, things go awry after the following actions - Flip - segue - Back - Flip. Repeat those actions and the tableview slowly makes its way down off the screen.
The code in FlipViewController is:
#interface FlipViewController ()
#property (strong, nonatomic) UIViewController *controller1;
#property (strong, nonatomic) UIViewController *controller2;
#property (strong, nonatomic) UIViewController *currentViewController;
#end
#implementation FlipViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.controller1 = [self.childViewControllers firstObject];
self.currentViewController = self.controller1;
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Storyboard" bundle:nil];
self.controller2 = [storyboard instantiateViewControllerWithIdentifier:#"Sweets Controller"];
[self addChildViewController:self.controller2];
}
- (IBAction)flip:(id)sender {
if (self.currentViewController == self.controller1) {
[self transitionFromViewController:self.controller1 toViewController:self.controller2 duration:0.3 options:UIViewAnimationOptionTransitionFlipFromRight animations:nil completion:^(BOOL finished) {
[self.controller2 didMoveToParentViewController:self];
self.currentViewController = self.controller2;
}];
}
else {
[self transitionFromViewController:self.controller2 toViewController:self.controller1 duration:0.3 options:UIViewAnimationOptionTransitionFlipFromRight animations:nil completion:^(BOOL finished) {
[self.controller2 didMoveToParentViewController:self];
self.currentViewController = self.controller1;
}];
}
}
#end
Some debugging seems to indicate the bounds of the UITableView is getting confused upon return. Do I have an AutoLayout problem or is my approach with transitionFromViewController: the wrong way to go about getting this effect? Any suggestions would be appreciated, I've been stuck on this bug for too long!?
I have a problem in my app.
When I pass a UIView to a SecondViewController in this way
SecondViewController *second = [self.storyboard instantiateViewControllerWithIdentifier:#"secondviewcontroller"];
second.currentView = currentView;
(second.currentView is my property in SecondViewController and I pass to it my view)
At this point, it's all ok and my SecondViewController show correctly my currentView, but when I retund in FirstViewController my currentView disappear!!!
Why? Can you help me? thanks
UPDATE
in FirstViewController.h
IBOutlet UIView *currentView;
in FirstViewController.m
SecondViewController *second = [self.storyboard instantiateViewControllerWithIdentifier:#"secondviewcontroller"];
second.currentView = currentView;
[self presentViewController:second animated:NO completion:nil];
in SecondViewController.h
#property (nonatomic, strong) UIView *currentView;
in SecondViewController.m
- (void) viewDidAppear:(BOOL)animated{
[self.view addSubview:self.currentView];
self.currentView.center = CGPointMake(self.view.bounds.size.width/2, self.view.bounds.size.height/2);
}
this is my code in the two ViewControllers, and when I return in FirstViewController currentView there isn't... it disappear...
A view only have super view, so when you add the view to SecondViewController you care adding it to its view hierarchy and thus removing to from your current view controller. When you call something like:
- (void) viewDidLoad{
[super viewDidLoad];
// Here the view is removed the first view and placed on the second view.
[self.view addSubView:self.secondView];
}
Thus if you want to add it back in the first view you need to add it again to view hierarchy of the first view.
A beter solution would be pass the data to the next view controller, not to view but the model. Like pass the model object holding the data presented in the view.
I solved my issue!!!
When I return in FirtsViewController I must add a new time my view in my self.view at last position, in this way:
[self.view addSubview:currentView];
[currentView setCenter:initially_center_view];
in this way I see my view!!!
I am running into a weird problem. I am using a UISegmentedControl to switch between two views. When the UISegmentedViewController comes on screen, the default view(view1) is loaded.View1 looks exactly as it should, but when I click the UISegmentedControl to change views, view2 does not look properly and is cut off. When I switch back to view1 now it also exhibits the same behavior. I am attaching the code I am using for this UISegmentedViewController below - I found it in a SO post. I will attach pictures for clarity:
Edit: Also, the last tableview cell in the Courses section cannot be properly scrolled to. Why is the view getting cut off?
Code:
//
// PCFSegmentedRateViewController.m
// Purdue Course Sniper
//
// Created by Kamran Pirwani on 2/5/13.
// Copyright (c) 2013 Kamran Pirwani. All rights reserved.
//
#import "PCFSegmentedRateViewController.h"
#interface PCFSegmentedRateViewController ()
// Array of view controllers to switch between
#property (nonatomic, copy) NSArray *allViewControllers;
// Currently selected view controller
#property (nonatomic, strong) UIViewController *currentViewController;
#end
#implementation PCFSegmentedRateViewController
#synthesize classRating,professorRating,segmentedControl;
- (void)viewDidLoad
{
[super viewDidLoad];
[segmentedControl addTarget:self action:#selector(indexDidChangeForSegmentedControl:) forControlEvents:UIControlEventValueChanged];
classRating = [[self storyboard] instantiateViewControllerWithIdentifier:#"ClassRate"];
professorRating = [[self storyboard] instantiateViewControllerWithIdentifier:#"Professor"];
// Add A and B view controllers to the array
self.allViewControllers = [[NSArray alloc] initWithObjects:classRating, professorRating, nil];
[self cycleFromViewController:self.currentViewController toViewController:[self.allViewControllers objectAtIndex:self.segmentedControl.selectedSegmentIndex]];
}
#pragma mark - View controller switching and saving
- (void)cycleFromViewController:(UIViewController*)oldVC toViewController:(UIViewController*)newVC {
// Do nothing if we are attempting to swap to the same view controller
if (newVC == oldVC) return;
// Check the newVC is non-nil otherwise expect a crash: NSInvalidArgumentException
if (newVC) {
// Set the new view controller frame (in this case to be the size of the available screen bounds)
// Calulate any other frame animations here (e.g. for the oldVC)
newVC.view.frame = CGRectMake(CGRectGetMinX(self.view.bounds), CGRectGetMinY(self.view.bounds), CGRectGetWidth(self.view.bounds), CGRectGetHeight(self.view.bounds));
// Check the oldVC is non-nil otherwise expect a crash: NSInvalidArgumentException
if (oldVC) {
// Start both the view controller transitions
[oldVC willMoveToParentViewController:nil];
[self addChildViewController:newVC];
// Swap the view controllers
// No frame animations in this code but these would go in the animations block
[self transitionFromViewController:oldVC
toViewController:newVC
duration:0.25
options:UIViewAnimationOptionLayoutSubviews
animations:^{}
completion:^(BOOL finished) {
// Finish both the view controller transitions
[oldVC removeFromParentViewController];
[newVC didMoveToParentViewController:self];
// Store a reference to the current controller
self.currentViewController = newVC;
}];
} else {
// Otherwise we are adding a view controller for the first time
// Start the view controller transition
[self addChildViewController:newVC];
// Add the new view controller view to the ciew hierarchy
[self.view addSubview:newVC.view];
// End the view controller transition
[newVC didMoveToParentViewController:self];
// Store a reference to the current controller
self.currentViewController = newVC;
}
}
}
- (IBAction)indexDidChangeForSegmentedControl:(UISegmentedControl *)sender {
NSUInteger index = sender.selectedSegmentIndex;
if (UISegmentedControlNoSegment != index) {
UIViewController *incomingViewController = [self.allViewControllers objectAtIndex:index];
[self cycleFromViewController:self.currentViewController toViewController:incomingViewController];
}
}
#end
I have the delegate working as the data is being passed from the modal to the presenting view controller. But the presenting view controller isn't showing the data it receives from the modal. I looked at other posts and they say to use the delegate/protocol method, but don't explain how/why the presenting VC refreshes. I'm assuming my delegate is setup incorrectly. Otherwise, what is the method to refresh the data? I've checked and the viewWillAppear and viewDidAppear doesn't get called.
SCCustomerDetailVC.h (Presenting VC)
#import "SCCustomersVC.h"
#interface SCCustomerDetailVC : UIViewController <SCCustomersVCDelegate>
#property (atomic, strong) SCCustomer *customer;
#property (strong, nonatomic) IBOutlet UIButton *changeCustomerButton;
- (IBAction)changeCustomerButtonPress:(UIButton *)sender;
#end
SCCustomerDetailVC.m (Presenting VC)
- (IBAction)changeCustomerButtonPress:(UIButton *)sender
{
UINavigationController *customersNC = [self.storyboard instantiateViewControllerWithIdentifier:#"customersNC"];
SCCustomersVC *customersVC = (SCCustomersVC *)customersNC.topViewController;
customersVC.delegate = self;
[self presentViewController:customersNC animated:YES completion:nil];
}
//Protocol methods
- (void)passCustomer:(SCCustomer *)customer
{
self.customer = customer;
//At this point, self.customer has the correct reference
[self dismissViewControllerAnimated:YES completion:nil];
}
SCCustomersVC.h (Modal VC)
#import "SCCustomersVCDelegate.h"
#class SCCustomerDetailVC;
#interface SCCustomersVC : UIViewController <UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate>
#property (weak, nonatomic) id <SCCustomersVCDelegate> delegate;
#end
SCCustomersVC.m (Modal VC)
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
SCCustomer *customer = [self customerAtIndexPath:indexPath];
[self.delegate passCustomer:customer];
}
SCCustomersVCDelegate.h
#class SCCustomer;
#protocol SCCustomersVCDelegate <NSObject>
#optional
- (void)passCustomer:(SCCustomer *)customer;
#end
I think you're nearly there. EDIT - just learned here that viewWillAppear behavior is different in iOS>5. You still want view will appear to update your view with your model state, since it needs to do that on initial presentation.
And it's fine to call it from either your modal vc or from within the delegate method. So add code to viewWillAppear that updates the view with the view controller's model state...
// SCCustomerDetailVC.m
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// making up an IBOutlet called someLabel
// making up a model method (description) that returns a string representing your model
self.someLabel.text = [self.customer description];
}
Then either from the presented vc or the delegate, call viewViewWillAppear:
- (void)passCustomer:(SCCustomer *)customer
{
self.customer = customer;
[self viewWillAppear:YES];
[self dismissViewControllerAnimated:YES completion:^{}];
}
You should reload your UI after setting the "customer"
- (void)passCustomer:(SCCustomer *)customer
{
self.customer = customer;
//At this point, self.customer has the correct reference
[self dismissViewControllerAnimated:YES completion:^{
// reload your UI here or call a method which will reload your ui
// e.g. for tableView it will be [tableView reload];
}];
}
My app allows the user to switch between two different modal view controllers (for two different styles of data entry). The code below used to work (in iOS 4.3 and earlier):
UIViewController * parent = current.parentViewController;
[current dismissModalViewControllerAnimated:NO];
svc.modalPresentationStyle = UIModalPresentationFormSheet;
[parent presentModalViewController:svc animated:NO];
[svc release];
but no longer (in iOS 5) - the "current" view controller dismisses, but "svc" is not presented.
Any idea why it broke (i.e. what did I do wrong)?
Any idea how to do it "right" (so that it works on 5.0 as well as 4.3 and earlier)?
Jeff Hay was totally right in his comment except for one thing. You should do it in the -viewDidAppear: method of the view controller which originally presented the first modal view controller.
Example:
// MyViewController.h
#interface MyViewController : UIViewController {
BOOL _shouldPresentSecondModalViewController;
}
#end
// MyViewController.m
#implementation MyViewController
- (void)viewDidAppear:(BOOL)animated {
if(_shouldPresentSecondModalViewController) {
UINavigationController *myNavCon;
// Code to create second modal navigation controller
[self presentModalViewController:myNavCon animated:YES];
_shouldPresentSecondModalViewController = NO;
}
}
- (void)presentFirstViewController {
UINavigationController *myNavCon;
// Code to create the first navigation controller
_shouldPresentSecondModalViewController = YES;
[self presentModalViewController:myNavCon animated:YES];
}
#end
EDIT:
Now, if you want to pass data between the two modal view controllers, you can use a delegate.
// FirstModalViewControllerDelegate.h
#protocol FirstModalViewControllerDelegate
#optional
- (void)controller:(FirstModalViewControllerDelegate *)vc shouldShowData:(id)anyType;
#end
// MyViewController.h
#interface MyViewController : UIViewController <FirstModalViewControllerDelegate> {
id _dataToDisplay;
}
#end
// MyViewController.m
#implementation MyViewController
- (void)viewDidAppear:(BOOL)animated {
if(_dataToDisplay != nil) {
UINavigationController *myNavCon;
// Code to create second modal navigation controller
[self presentModalViewController:myNavCon animated:YES];
[_dataToDisplay release];
_dataToDisplay = nil;
}
}
- (void)presentFirstViewController {
UINavigationController *myNavCon;
FirstModalViewController *myCon;
// Code to create the first modal view controller
[myCon setDelegate:self];
myNavCon = [[UINavigationController alloc] initWithRootViewController:myCon];
[self presentModalViewController:myNavCon animated:YES];
[myNavCon release];
}
- (void)controller:(FirstModalViewControllerDelegate *)vc shouldShowData:(id)anyType {
/* This method will get called if the first modal view controller wants to display
some data. If the first modal view controller doesn't call this method, the
_dataToDisplay instance variable will stay nil. However, in that case, you'll of
course need to implement other methods to, like a response to a Done button, dismiss
the modal view controller */
[self dismissModalViewController];
_dataToDisplay = [anyType retain];
}
#end