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
Related
I'm writing a custom UIStoryboardSegue to animate the transitions between views. When a segue is triggered, each controller (source and destination) is asked to animate accordingly. Here's how:
- (void)perform
{
UIViewController * sourceViewController = [self sourceViewController];
UIViewController * destinationViewController = [self destinationViewController];
UIViewController * containerViewController = [self containerViewController];
UIView * sourceView = sourceViewController.view;
UIView * destinationView = destinationViewController.view;
UIView * containerView = containerViewController.view;
// The container view is as large as the union of the two other views
CGRect sourceFrame = sourceView.frame;
CGRect destinationFrame = destinationView.frame;
CGRect containerFrame = CGRectUnion(sourceFrame, destinationFrame);
[containerView setFrame:containerFrame];
// The two views are presented in a container view; this way no one fights about who gets what
[sourceView removeFromSuperview];
[containerView addSubview:sourceView];
[containerView addSubview:destinationView];
// [sourceViewController presentViewController:containerViewController animated:NO completion:^{
// NSLog(#"done presenting");
// }];
// NSLog(#"done calling presenting");
id <HyTransition> presentingTransition = [self presentingTransitionForViewController:destinationViewController];
id <HyTransition> dismissingTransition = [self dismissingTransitionForViewController:sourceViewController];
// Perform the transitions
[presentingTransition performWithTransitionContext:self.presentingContext];
[dismissingTransition performWithTransitionContext:self.dismissingContext];
}
The problem is when presenting the containing view. It works well if the source view controller is a standard view controller, but not if it's UINavigationController or others of the kind, which don't add their views to the view hierarchy. When doing so, I get this:
Warning: Attempt to present <HyPresentationViewController: 0x7fdac2d34b60> on <HyRootController: 0x7fdac2e28590> whose view is not in the window hierarchy!
How can I present the containing view?
Edit: notice that I do not require a containing view controller - a containing view is enough.
I'm using a custom rootViewController, and am using the code below to access it from any UIViewController
#implementation UIViewController(CustomRootVC)
- (CustomRootVC*)customViewController
{
UIViewController *parent = self;
Class customClass = [CustomRootVC class];
while ( nil != (parent = [parent parentViewController]) && ![parent customClass] )
{
}
return (id)parent;
}
#end
If I call self.customViewController on viewDidLoad I get nil. If I call it on willAppear I get the reference I expect.
I'm guessing this is something to do with the order I add the view controllers to my view controller container (i.e. viewDidLoad is called before the view controller has been added to customViewController and so it isn't a parent), but I can't spot anything obvious. I add the view controllers as follows:
- (void)addViewController:(UIViewController*)controller toWrapper:(PLSliderView*)wrapper{
[self addChildViewController:controller];
[self addView:controller.view ToWrapper:wrapper];
[self.viewControllers addObject:controller];
[controller didMoveToParentViewController:self];
}
In particular, the issue seems to be with adding a new view controller and view as follows:
- (void)replaceTopOfStackWithViewController:(UIViewController *)newController animated: (BOOL)animated {
UIViewController *oldController = self.currentController;
[self addChildViewController:newController];
[self transitionFromViewController:oldController
toViewController:newController
duration:1.0
options:UIViewAnimationOptionTransitionCrossDissolve
animations:nil
completion:^(BOOL finished) {
[self.rightViewController didMoveToParentViewController:self];
[self removeViewController:oldController];
[self queryDimensions:#"REPLACE"];
[self.view setUserInteractionEnabled:YES];
self.currentController = newController
}];
}
The view controller hasn't been added to the view controller hierarchy at the time viewDidLoad is called, hence it has no parent view controller, hence your function returns nil as expected.
this piece of code (parent = [parent parentViewController]) sets parent equal to self.parentViewController. If self does not have a parent view controller at the time this is called, it would return nil as expected.
more broadly, I'm not sure what you're trying to accomplish with this code, or why you need to use a category. it seems like that sort of behavior would make more sense in a UIViewController subclass.
I'm trying to modally display a UIWebview on top of the currently displayed view controller. The code to popup the webview lives in a static library and has no idea about the view controller hierarchy of the app that it lives in. The code below works for my test cases, but not sure if it would work for all possible view controller hierarchies.
UIViewController *rootViewController = [[[UIApplication sharedApplication] keyWindow] rootViewController];
UIViewController *presentedVC = [rootViewController presentedViewController];
if (presentedVC) {
// We have a modally displayed viewcontroller on top of rootViewController.
if (!presentedVC.isBeingPresented) {
[presentedVC presentViewController:vc animated:YES completion:^{
//
}];
} else {
rootViewController presentViewController:vc animated:YES completion:^{
//
}];
}
Does the presentedViewController always give the currently visible view controller?
If what you want to do is to display the UIWebView modally on top of whatever View Controller is displayed in your app, you don't need the "topmost" one. Getting the Root View Controller will suffice. I do this all the time whenever I want to present a View on top of everything else.
You only need a reference to the root view controller in your library:
self.rootVC = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
Having this reference you have two options now:
The first one is to use UIViewController's method presentViewController:animated:completion: to display another View Controller which will contain your UIWebView
The second option is to fake the modal view controller by adding a subview that covers the entire screen, has a (semi)transparent background, and contains the view you want to display "modally". Here is an example:
#interface FakeModalView : UIView // the *.h file
#property (nonatomic, retain) UIWebView *webView;
#property (nonatomic, retain) UIView *background; // this will cover the entire screen
-(void)show; // this will show the fake modal view
-(void)close; // this will close the fake modal view
#end
#interface FakeModalView () // the *.m file
#property (nonatomic, retain) UIViewController *rootVC;
#end
#implementation FakeViewController
#synthesize webView = _webView;
#synthesize background = _background;
#synthesize rootVC = _rootVC;
-(id)init {
self = [super init];
if (self) {
[self setBackgroundColor: [UIColor clearColor]];
_rootVC = self.rootVC = [[[[[UIApplication sharedApplication] delegate] window] rootViewController] retain];
self.frame = _rootVC.view.bounds; // to make this view the same size as the application
_background = [[UIView alloc] initWithFrame:self.bounds];
[_background setBackgroundColor:[UIColor blackColor]];
[_background setOpaque:NO];
[_background setAlpha:0.7]; // make the background semi-transparent
_webView = [[UIWebView alloc] initWithFrame:CGRectMake(THE_POSITION_YOU_WANT_IT_IN)];
[self addSubview:_background]; // add the background
[self addSubview:_webView]; // add the web view on top of it
}
return self;
}
-(void)dealloc { // remember to release everything
[_webView release];
[_background release];
[_rootVC release];
[super dealloc];
}
-(void)show {
[self.rootVC.view addSubview:self]; // show the fake modal view
}
-(void)close {
[self removeFromSuperview]; // hide the fake modal view
}
#end
If you have any other questions just let me know.
Hope this helps!
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;
}];
}
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