Replacing View Controllers programmatically? - iphone

I'm using a library (ViewDeck) to have a sliding view. I believe my problem would apply to any other library.
I have the following code in my Initial View Controller:
#import "InitialViewController.h"
#implementation InitialViewController
- (id)initWithCoder:(NSCoder *)aDecoder
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard"
bundle:nil];
self = [super initWithCenterViewController:[storyboard
instantiateViewControllerWithIdentifier:#"middleViewController"]
leftViewController:[storyboard
instantiateViewControllerWithIdentifier:#"leftViewController"]];
return self;
}
#end
There are times when I need to present a ViewController modally and then, depending on the user's input, swap the current View Controllers;
For example, I would need to swap middleViewController for mainViewController and leftViewController for menuViewController.
I can't come up with a way for doing this. I thought of using delegates on the Initial View Controller, but I believe that delegate code is lost when I call self = [super initWith...] on the code above.
Is there a way to swap these controllers? Should I be replacing the rootViewController? If so, how?

I ended up using SWRevealViewController instead of ViewDeck. Their implementation is, I believe, cleaner and easier to customize.

Related

How to send data to the RootViewController

Here is my problem:
I have a basic application with a UIViewController embedded in a NavigationController. It is also the RootViewController of the application. From there I have a push segue to a normal UIViewControllerand a second push segue to a UITableViewController with its own UIViewController for the detailed view.
In the root view there is an instance of the class whose purpose is to send message with a define protocol.
In the table view the user will select the type of message he wants to send and in the detail view the content of that particular type of message.
Now the user has specified everything and I want him to push a "send" button. That button must do two things: pop back to the root view and send the user defined message by the protocol class instance.
I can do the pop back just fine with:
[self.navigationController popToRootViewControllerAnimated:true];
but I have no idea how to send the message (a class instance) back to the root view. The application is still fresh so I can completely change the structure if this one is not correct.
The best for me would be to access the protocol class instance from everywhere (I will need it in the other UIViewController) but I am not sure how to do that so that's why I thought of sending the message back to the root view.
If you know how to do one of the two above please give me a hand!
Cheers.
EDIT: Technically the NavigationController is the initial ViewController so I am not really sure who is the RootViewController anymore.
First: You could do: (only works when your view has been added to a the window)
[self.view.window.rootviewcontroller doSomething];
Second option is to define a property on your appDelegate:
#property (nonatomic, strong) UIViewController *root;
And call it through:
AppDelegate *appDelegate= (YourAppDelegateClass *) [[UIApplication sharedApplication] delegate];
[appDelegate.root doSomething];
you can Create an application object and assign the message to it. and use it on your root view controller. if i understood your question correctly this might help.
You could try:
UIApplication *myApp = [UIApplication sharedApplication];
UIWindow *frontWindow = [myApp.windows lastObject];
UIViewController *myRootViewController = frontWindow.rootViewController;
You Can also send Notification whenever you move to root view controller
by adding observer to it.
one way is to use protocol and other way is to pass your root view controller instance to your tableviewcontroller(via view controller) using your custom init method like:
UIViewController.m
- (id)initWithRoot:(id)rootInstance
withNibNameOrNil:(NSString *)nibNameOrNil
bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self)
{
self.root = (RootView *)rootInstance;
}
}
RootViewController.m
viewcontrollerInstance = [[viewcontroller alloc] initWithRoot:self withNibNameOrNil:#"viewcontroller"
bundle:[NSBundle mainBundle]];
[self.navigationController pushViewController:viewcontrollerInstance animated:YES];
UITableViewController.m
- (id)initWithRoot:(id)rootInstance
withNibNameOrNil:(NSString *)nibNameOrNil
bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self)
{
self.root = (RootView *)rootInstance;
}
}
ViewController.m
tableViewInstance = [[tablecontroller alloc] initWithRoot:self withNibNameOrNil:#"tablecontroller"
bundle:[NSBundle mainBundle]];
[self.navigationController pushViewController:tableViewInstance animated:YES];
UITableViewController.m
now on table view controller use your root instance(come from view controller) to call the function of root view controller like:
[self.root displayMessage:message];
sorry for the typo. hope this will help.

How to change a UIViewController class name in a UIStoryboard at runtime

I have this UIViewController set up in in my storyboard, with all the outlets, views, and constraints I need. Perfect. Let's call this WatchStateController, it'll serve as an abstract parent class.
I then have this subclass of WatchStateController, called WatchStateTimeController, which will have the functionality I need for a particular state of the application.
Because I am trying to use the 1 view controller in the UIStoryboard, I'm having some problems in instantiating a WatchStateTimeController as type WatchStateTimeController - it instantiates as WatchStateController.
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
WatchStateTimeController *timeController = (WatchStateTimeController *)[mainStoryboard instantiateViewControllerWithIdentifier:#"WatchStateController"];
This is because the "Class" field in the storyboard's Identity Inspector is set to "WatchStateController". So the question is, how do I merely change this classname set in the Identity Inspector at runtime?
NOTE: ignore why I'm trying to do this and concentrate on how. If you really must know why, you can read up on the Strategy design pattern.
One slightly dirty workaround I'm using to force storyboard to be compatible with the strategy pattern: I write a custom allocator in the base (abstract) view controller that returns an instance of the desired concrete view controller subclass, before the storyboard mechanism gets over.
For this to work, you have to tell the base class which subclass you want to instantiate.
So, in the base controller:
Class _concreteSubclass = nil;
+ (void) setConcreteSubclassToInstantiate:(Class)c {
_concreteSubclass = c;
}
+ (id)allocWithZone: (NSZone *)zone {
Class c = _concreteSubclass ?: [self class];
void *object = calloc(class_getInstanceSize(c), 1);
*(Class *)object = c;
return (id)CFBridgingRelease(object);
}
This instantiates enough memory for the ivars of the subclass too.
The view controller type of "MyViewController" known to the storyboard is just "BaseViewController"; but then, where you ask the storyboard to instantiate the view controller, you do something like this:
[BaseViewController setConcreteSubclassToInstantiate:[SomeSubclassOfBaseViewController class]];
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"Main" bundle: nil];
SomeSubclassOfBaseViewController *vc = (SomeSubclassOfBaseViewController *)[mainStoryboard instantiateViewControllerWithIdentifier:#"MyViewController"];
[self presentViewController:vc animated:NO completion:^{}];
The concrete view controller is instantiated and shown without a hitch.
Here's an example of the strategy pattern using a helper object, as I described in the comments:
#class WatchStateController;
#protocol WatchStateStrategy <NSObject>
- (void)doSomeBehaviorPolymorphically:(WatchStateController *)controller;
#end
#interface WatchStateController
// or call this a delegate or whatever makes sense.
#property (nonatomic) id <WatchStateStrategy> strategy;
#end
#implementation WatchStateController
- (void)someAction:(id)sender
{
[self.strategy doSomeBehaviorPolymorphically:self];
}
#end
#interface WatchStateTimeStrategy <WatchStateStrategy>
#end
#implementation WatchStateTimeStrategy
- (void)doSomeBehaviorPolymorphically:(WatchStateController *)controller
{
// here's one variation of the behavior
}
#end
#interface WatchStateAnotherStrategy <WatchStateStrategy>
#end
#implementation WatchStateAnotherStrategy
- (void)doSomeBehaviorPolymorphically:(WatchStateController *)controller
{
// here's another variation of the behavior
}
#end
And to set this up when you are presenting your view controller, assign the appropriate helper object (instead of attempting to change the subclass of the view controller itself):
WatchStateController *viewController = [storyboard instantiateViewControllerWithIdentifier:#"WatchStateController"];
if (useTimeStrategy) {
viewController.strategy = [WatchStateTimeStrategy new];
} else {
viewController.strategy = [WatchStateAnotherStrategy new];
}
The advantages I see to this approach compared to subclassing the view controller:
It more closely aligns with SOLID principles, especially single responsibility principle, open/closed principle, etc.
Small, focused helper classes, possibly having few or no UI dependencies depending on what they need to do, make for easier unit testing if you plan to write tests
It more closely follows the design patterns and structural patterns already in place in iOS (using delegates, and letting storyboards/xibs instantiate view controllers the normal way)
Removes logic from the view controller. With iOS it's so easy to get a large view controllers with too much logic; I think we should always be looking for opportunities to improve this

Transparent ViewController to See Parent Below?

I would like to modally add a view controller with a transparent background, so the parent view controller beneath can be seen. (This is in an app for iPhone, not for iPad.)
I have tried this:
TextFieldViewController *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"TextFieldVC"];
vc.modalPresentationStyle = UIModalPresentationCurrentContext;
vc.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self.navigationController presentViewController:vc animated:YES completion:^{}];
Without luck and given the view controller a clear color background. My view controller is in a storyboard if that changes anything.
#Josh Kahane
set the view controller that will present the transparent view controller with this code at the -ViewDidLoad
and be sure to set the alpha channel of the UIViewController View to be lower then 1.
Code:
self.modalPresentationStyle = UIModalPresentationCurrentContext;
I have been searching for the solution. Now thanks to iOS 8. They has introduced couple of new modalPresentationStyle. one among them is UIModalPresentationOverCurrentContext. Used the same to solve the this issue.
viewcontroller.modalPresentationStyle = UIModalPresentationOverCurrentContext;
Hope this helps.
For completeness and having it up-to-date, I am also adding the solution for Swift:
either
viewController.modalPresentationStyle = .CurrentContext
being - A presentation style where the content is displayed over only the presenting view controller’s content.
or
viewController.modalPresentationStyle = .OverCurrentContext
being - A presentation style where the content is displayed over only the parent view controller’s content. The views beneath the presented content are not removed from the view hierarchy when the presentation finishes. So if the presented view controller does not fill the screen with opaque content, the underlying content shows through.
Depending on the requirements and circumstances for the presentation.
It's possible although there are a bunch of steps which can be easily forgotten. After struggling with myself to get this working for 4 hours I came up with the following solution.
1 - Create a View Controller like any other.
Header file
#import "DDBaseViewController.h"
#interface DDHomeSearchLoadingViewController : UIViewController
#end
Implementation file
#import "DDHomeSearchLoadingViewController.h"
#interface DDHomeSearchLoadingViewController ()
#property (weak, nonatomic) IBOutlet UIActivityIndicatorView *activityMonitor;
#property (weak, nonatomic) IBOutlet UIView *modalView;
#end
#implementation DDHomeSearchLoadingViewController
#pragma mark - UIViewController lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
[self setupUI];
}
-(void) setupUI
{
[self makeRoundedCorner:self.modalView AndCornerRadius:6.0f];
[self.activityMonitor startAnimating];
}
-(void) makeRoundedCorner:(UIView*) view AndCornerRadius:(float) cornerRadius
{
[view.layer setCornerRadius:cornerRadius];
[view.layer setMasksToBounds:YES];
}
#end
2 - Set your container view background color as ClearColor
3 - Add a UIView which will be presented like an overlay
4 - Add a UIView which will be presented like a Dialog Box over the overlay UIView
Make sure it's outside the overlay view when you add/move it. For some reason when you move it using the mouse it adds to overlay UIView automatically. ( Freaking annoying to be honest )
5 - Set the Storyboard ID for the View you've created
6 - Finally add this piece of code wherever you wan't call it ( Assuming it's a UiViewController too )
DDHomeSearchLoadingViewController* ddHomeSearchLoadingViewController = [[UIStoryboard storyboardWithName:#"Main" bundle:nil] instantiateViewControllerWithIdentifier:#"DDHomeSearchLoadingViewController"];
self.navigationController.modalPresentationStyle = UIModalPresentationCurrentContext;
[self presentViewController:ddHomeSearchLoadingViewController animated:YES completion:nil];
I hope it helps you guys out
Cheers
Answer for Swift 5.
TransparentViewController
self.view.backgroundColor = .clear
PresentingViewController
let viewController = TransparentViewController()
viewController.modalPresentationStyle = .overFullScreen
navigationController.present(viewController, animated: false)
Swift 3 -
Try vc.modalPresentationStyle = .overFullScreen
let vc = self.storyboard!.instantiateViewControllerWithIdentifier("ExampleViewController") as! ExampleViewController
vc.view.backgroundColor = UIColor.clearColor()
vc.modalPresentationStyle = .overFullScreen
self.presentViewController(vc, animated: true, completion: nil)
you can make changes in storyboard just by doing below options and also reducing parental view opacity. Using Storyboard: No need to write any code to achieve this
UIStoryboard *story = [UIStoryboard storyboardWithName:#"Main" bundle:[NSBundle mainBundle]];
ChooseController *sec = [story instantiateViewControllerWithIdentifier:#"Controller"];
sec.modalPresentationStyle = UIModalPresentationOverCurrentContext;
sec.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentViewController:sec animated:YES completion:^{}];
Note: Present controller superview alpha value must be below 1 like 0.5 alpha like that.
When you modally present a view controller the , it goes on the stack which in turn hides the view controller below it. So all you can do is to present a transparent view with animation similar to modally presenting a view controller.

ViewController loaded in the Background after creating it from MapKit (didSelectAnnotationView)

I'm trying to create from the delegate method didSelectAnnotationView a ViewController.
I've got following Code:
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
{
DestinationViewController *destinationViewController = [[DestinationViewController alloc] init];
NSNumber *mynumberId = [NSNumber numberWithInt:1];
destinationViewController.mynumberId = mynumberId;
[self.navigationController pushViewController:destinationViewController animated:YES];
}
After I tap on a Annotation I'm seeing just a black ViewController Screen with the Navigation Bar. But the Log tells me that everything is loaded correctly. So it seems like my Destination ViewController is somewhere in the background.
If you create your VC from a Nib, you need to use initWithNibName: rather than just init.
If you load the view programmatically, you need to check your viewDidLoad method.
It is not a mapKit issue, but you are not loading your VC and its view the way you should.
I've researched on the Apple Developer Site and found the answer for my question. I'm using Storyboards, and here the code snippet from Apple:
- (IBAction)presentSpecialViewController:(id)sender {
UIStoryboard *storyboard = self.storyboard;
SpecialViewController *svc = [storyboard instantiateViewControllerWithIdentifier:#"SpecialViewController"];
// configure the new view controller explicitly here.
[self presentViewController:svc animated:YES completion:nil];
}
and here the Link where you can find more Info about View Controller Programming:
http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/UsingViewControllersinYourApplication/UsingViewControllersinYourApplication.html

How can I manually switch between UIViewControllers in storyboard?

All I need is to view a UIView controller in same storyboard file manually with code. I use storyboard to make all forms and connections. My application starts in navigation controller, which provides me access to UIView (LoginViewController) and then it goes to tab bar controller, which provides 4 UIViews. According to every UIView I have .h and .m files. I know about segue method, it is simple, but I need manual method. Maybe I am doing something wrong.
I was trying to use this method for pushing view controller in IBAction:
[self.view pushViewController:LoginViewController animated:YES];
But it makes an error:
Unexpected interface name ‘LoginViewController’: expected expression
It took a lot of time to figure out what is wrong, but I had not succeed.
Here is my RollEnemyController.m file:
// RollEnemyController.m
#import "RollEnemyController.h"
#import "LoginViewController.h"
#implementation RollEnemyController;
#synthesize AttackButtonPressed;
- (IBAction)AttackButtonPressed:(id)sender {
LoginViewController* controller = [[LoginViewController alloc] initWithNibName:#"LoginViewController" bundle:nil];
[self.view pushViewController:controller];
}
#end
And this is header file:
// RollEnemyController.h
#import <UIKit/UIKit.h>
#interface RollEnemyController : UIViewController
- (IBAction)RollButtonPressed:(id)sender;
#property (weak, nonatomic) IBOutlet UIButton *AttackButtonPressed;
#end
I'm guessing that you are using a UINavigationController. Then you can simply do like this:
LoginViewController *controller = [[LoginViewController alloc] initWithNibName:#"LoginViewController" bundle:nil];
[self.navigationController pushViewController:controller animated:YES];
Update:
If you are using a UIStoryboard, you can set the identifier of your new viewcontroller, and then push it onto your navigationController. To set the identifier, choose your view, open the Attributes Inspector, and set the identifier ("LoginIdentifier" in my example). Then you can do this:
LoginViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"LoginIdentifier"];
[self.navigationController pushViewController:controller animated:YES];
As a sidenote, I see that you are using capital characters for your methods. You should probably try to avoid that, and instead use lowered first-characters in your method names. And since you say you are learning Objective-C, you should check out this awesome thread here on SO: link.
Update 2:
Here is a zip file with a project showing how to do this. :-)
hello try to use this code
Storyboard put ID = "xxx * Name Desire"
mark use StoryboarID
UIStoryboard * storyboard = self.storyboard;
DetailViewController * detail = [storyboard instantiateViewControllerWithIdentifier: # "xxx * Name Desire"];
[self.navigationController pushViewController: detail animated: YES];
In this statement:
[self.view pushViewController:LoginViewController animated:YES];
it seems you are trying to push a class. You should push an object, your actual controller:
LoginViewController* controller = [[LoginViewController alloc] init...];
[self.view pushViewController:controller animated:YES];
this will at least compile, and if all the rest is fine, also give you the second controller.
EDIT:
I missed one point. You are pushing the view controller on to a view. That makes no sense, you should push the controller on to the navigation controller:
<AppDelegate> *del = (AppDelegate*)[UIApplication sharedApplication].delegate;
[del.navigationController pushViewController:controller animated:YES];
This is true, at least, if you created your project from the Navigation-based template (which creates an application delegate with a reference to the navigation controller). Otherwise, please provide details about how you create the navigation controller.
You mentioned in a comment that you're using UIStoryboard. Are you aware of UIStoryboardSegue? All you have to do it control-drag from the button to the next view controller to establish a segue. Then you can choose the type of transition. Be aware that your view controllers need to be part of a UINavigationController in the storyboard to perform a "Push" animation.