I have a ECSSlidingViewController with a menu in the underLeftViewController that has a few different buttons that lead to different segues to display different UIViewControllers in the ECSSlidingViewController.topViewController. All the buttons on the menu work well and segue to the appropriate view controller.
However, I would like to be able to perform one of the segues from another UIViewController other than the ECSSlidingViewController.underLeftViewController and whenever I try to do this I get an EXC_BAD_ACCESS(1) error. In my logs the following information shows up.
Warning: Attempt to dismiss from view controller : 0xc061250> while a presentation or dismiss is in progress!
2014-02-08 23:36:51.772 andrew[19995:70b] * -[ECDynamicTransition retain]: message sent to deallocated instance 0x11a32710
- (CGRect)frameFromDelegateForViewController:(UIViewController *)viewController
topViewPosition:(ECSlidingViewControllerTopViewPosition)topViewPosition {
CGRect frame = CGRectInfinite;
NSObject *testSelf = self;
NSObject *testSelfDelegate = self.delegate; //This is where EXC_BAD_ACCESS(1) occurs
if ([(NSObject *)self.delegate respondsToSelector:#selector(slidingViewController:layoutControllerForTopViewPosition:)]) {
id<ECSlidingViewControllerLayout> layoutController = [self.delegate slidingViewController:self
layoutControllerForTopViewPosition:topViewPosition];
if (layoutController) {
frame = [layoutController slidingViewController:self
frameForViewController:viewController
topViewPosition:topViewPosition];
}
}
return frame;
}
Related
Hi im developing an app that has a parent view that then used containers to embed other views as seen below.
For now im only working with the left and centre container which are both table views. The main view or the Project screen view is my parent controller and i want it to pass data to and from the two child controller and i know for this the best option is to use delegates. However each example i have looked at that uses delegates, created and initialises a new view controller so for example lets say the left container embeds a view using the leftviewcontroller. Each example has this line of code.
LeftViewController *sampleProtocol = [[LeftViewController alloc]init];
LeftViewController.delegate = self;
Im thinking i dont need to create a new LeftViewController since it is embeded it is already in my list of child controllers. So my queston is how would i get the controller from the list of child controllers and set the parent as the delegate. I know i it is an array and i can use objectAtIndex but how do i know the order of items in the array will not change can i not call it but a tag or identifier? Thank you for any help sorry if the question is not that clear its my first time setting up delegates.
i know for this the best option is to use delegates.
In this case, I wouldn't be so sure. I think the best option would be to have a robust model and use KVO and notifications to signal updates between view controllers.
The direct answer to your question is not too bad.
for (UIViewController *viewController in self.childViewControllers) {
if ([viewController isKindOfClass:[LeftViewController class]]) {
LeftViewController *leftViewController = (id)viewController;
leftViewController.delegate = self;
break;
}
}
I think a minor improvement on this would be to use the segue. Make sure each of the containers have a named segue. In this example, the left view controller has a segue with the identifier "Load Child LeftViewController".
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"Load Child LeftViewController"]) {
LeftViewController *leftViewController = segue.destinationViewController;
leftViewController.delefate = self;
}
}
Its always better to use NSNotificationCenter for such complex mechanism.
*** put following code in LeftController.m ***
// *** Register a Notification to recieve a Data when something happens in Center controller ***
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receivedNotification:)
name:#"hasSomeData"
object:nil];
// *** create a method to receive Notification data ***
- (void)receivedNotification:(NSNotification *) notification {
if ([[notification name] isEqualToString:#"hasSomeData"])
{
// do your stuff here with data
NSLog(#"data %#",[notification object]);
}
}
*** when something happen in center controller post a notification to inform Left Controller ***
[[NSNotificationCenter defaultCenter] postNotificationName:#"hasSomeData" object:self];
//Secondvc.h
#protocol Sendmessage<NSObject>
#required
-(void)Object:(NSArray *)tosend;
#end
#interface Secondvc:UIViewcontroller{
id <Sendmessage> delegate;
}
#property(strong,nonatomic) id <Sendmessage> delegate;
#end
//Secondvc.m
#implementation Secondvc
#synthesize delegate;
-(void)viewDidLoad{
//Do Something here!
}
//Pass Some Value When a button event occured in Second vc
-(IBAction)Send_Data{
[self dismissViewControllerAnimated:Yes completion:nil];
[self.delegate Object:[NSArray Arraywithobjects:#"Hello",nil]];
}
#end
//FirstVc.h
#import "Secondvc.h"
#interface FirstVc<Sendmessage>
#end
//FirstVc.m
#implementation FirstVc
-(void)viewDidLoad{
Secondvc* Object=[[Secondvc alloc]init];
Object.delegate=self;
}
#pragma mark Secondvc Deklegate method implementation
-(void)Object:(NSArray *)tosend{
NSLog(#"Recieved data Form Second VC Is:\n%#",tosend);
}
#end
HTH!Enjoy Coding.
I am using SWRevealViewController to implement a side menue in my application. in storyboard i have set the sw_front segue. this segue is loading the MainView on every start of application. now i want to give the user the possibility to change the first displayed viewcontroller.
How can i do that? I know there is a property setFrontViewController: but where should i use this to load the front during application start?
I cant use it in the viewDidLoad of the frontViewController which i set in Storyboard. That could not be a solution, so i would need to do it in every view controller to grab and check the "should load another view controller as first or frontViewController"
I have understood what you have problem and what you want to do i have also the same kind of thing to do, so what i have done create the custom segue in the swrevealviewcontroller that i have also mention in my code.
and modified the two methods perform and loadstoryboardcontroller methods, you can also see in my code.
It will definetely work for you as well.
Have a happy coding cheers.
My code is below:
I have done this the code you need to change is below.
- (void)loadStoryboardControllers
{
if ( self.storyboard && _rearViewController == nil )
{
//Try each segue separately so it doesn't break prematurely if either Rear or Right views are not used.
#try
{
[self performSegueWithIdentifier:SWSegueRearIdentifier sender:nil];
}
#catch(NSException *exception) {}
#try
{
[self performSegueWithIdentifier:SWSegueFrontIdentifier sender:nil];
}
#catch(NSException *exception) {}
#try
{
[self performSegueWithIdentifier:SWSegueRightIdentifier sender:nil];
}
#catch(NSException *exception) {}
#try
{
[self performSegueWithIdentifier:SWSegueCustomIdentifier sender:nil];
}
#catch(NSException *exception) {}
}
}
#pragma mark - SWRevealViewControllerSegueSetController segue identifiers
NSString * const SWSegueRearIdentifier = #"sw_rear";
NSString * const SWSegueFrontIdentifier = #"sw_front";
NSString * const SWSegueRightIdentifier = #"sw_right";
NSString * const SWSegueCustomIdentifier = #"sw_custom";
#pragma mark - SWRevealViewControllerSegueSetController class
#implementation SWRevealViewControllerSegueSetController
- (void)perform
{
SWRevealControllerOperation operation = SWRevealControllerOperationNone;
NSString *identifier = self.identifier;
SWRevealViewController *rvc = self.sourceViewController;
UIViewController *dvc = self.destinationViewController;
if ( [identifier isEqualToString:SWSegueFrontIdentifier] )
operation = SWRevealControllerOperationReplaceFrontController;
else if ( [identifier isEqualToString:SWSegueRearIdentifier] )
operation = SWRevealControllerOperationReplaceRearController;
else if ( [identifier isEqualToString:SWSegueRightIdentifier] )
operation = SWRevealControllerOperationReplaceRightController;
else if ( [identifier isEqualToString:SWSegueCustomIdentifier])//your conditional segue
operation = SWRevealControllerOperationReplaceFrontController;
if ( operation != SWRevealControllerOperationNone )
[rvc _performTransitionOperation:operation withViewController:dvc animated:NO];
}
#jogshardik has the correct answer for my question but i found also another solution.
In your storyboard you are connecting a custom segue "sw_front" with your viewcontroller. This viewcontroller is on every initial start your first front viewcontroller. So you just have to check in this controller if you need to set another front viewcontroller.
i set up a notification in this "sw_front" viewcontroller and also in your Side Menue View Controller add the notification handler.
if you want to change the view just fire the notification and let do the notifaction handler the rest (set your setFrontViewController: property).
So this also works and is update stable if you need to keep attention on newer versions.
if this information is not enough for you, give me a hint and i will post an example code or project when i have time.
My Application is getting crashed with the following error.
-[PreviewViewController applicationWillSuspend]: message sent to deallocated instance 0x1806d9e0
My application have two view controllers one is HomeViewController and other one is PreviewViewController.
In home view controller i am displaying a table view. When selecting the row of table view i am presenting the preview view controller.
I selected one row then preview view controller is presented.
PreviewViewController *previewController = [[PreviewViewController alloc]initWithPreviewImage:[[kfxKEDImage alloc] initWithImage:imgCaptured] withSourceofCapture:_typeOfCapture typeOfDocumentCaptured:PHOTO];
[self presentViewController:previewController animated:YES completion:nil];
Dismissed the preview view controller.
[self dismissViewControllerAnimated:YES completion:nil];
Application goes into background then it is not crashed.
I selected two rows one after another. Application goes into background then it is crashed. I don't know why it is behaving like that. If anyone know the solution please tell me.
Thanks In Advance
I had this problem, it was caused by someone overriding 'dealloc' in a UIViewController category.
https://github.com/taphuochai/PHAirViewController/issues/13
#chrishulbert
Remove this:
- (void)dealloc
{
self.phSwipeHander = nil;
}
Replace dealloc with this:
/// This is so that phSwipeGestureRecognizer doesn't create a swipe gesture in *every* vc's dealloc.
- (BOOL)phSwipeGestureRecognizerExists {
return objc_getAssociatedObject(self, SwipeObject) ? YES : NO;
}
- (void)ph_dealloc
{
if (self.phSwipeGestureRecognizerExists) {
self.phSwipeHander = nil;
}
[self ph_dealloc]; // This calls the original dealloc.
}
/// Swizzle the method into place.
void PH_MethodSwizzle(Class c, SEL origSEL, SEL overrideSEL) {
Method origMethod = class_getInstanceMethod(c, origSEL);
Method overrideMethod = class_getInstanceMethod(c, overrideSEL);
if (class_addMethod(c, origSEL, method_getImplementation(overrideMethod), method_getTypeEncoding(overrideMethod))) {
class_replaceMethod(c, overrideSEL, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
} else {
method_exchangeImplementations(origMethod, overrideMethod);
}
}
/// Swizzle dealloc at load time.
+ (void)load {
SEL deallocSelector = NSSelectorFromString(#"dealloc"); // Because ARC won't allow #selector(dealloc).
PH_MethodSwizzle(self, deallocSelector, #selector(ph_dealloc));
}
Okay I am kind of new to IOS development, but I am writing an application where I am using a timer class to time out the user if they idle too long on any particular scene in my storyboard and it bumps the user back to the original scene/view. I have a single story board that is made up of several scenes/views(not sure what the correct word here is), and each scene has its own view controller.
I accomplish the timeout via the appdelegate class. See code below.
So I have the code working and it works great, but I am trying to make it so that it will ignore the timer if we are on the main scene.
I have googled this, read copious amounts of documentation, and have tried many things but so far I haven't been able to figure out how to get the currently viewed scene in the applicationDidTimeout method.
If I can get the name of the currently viewed scene/view, then I can choose to ignore the timer or not.
Does anyone know how to do this?
Thank you for your time.
#import "StoryboardAppDelegate.h"
#import "TIMERUIApplication.h"
#implementation StoryboardAppDelegate
#synthesize window = _window;
-(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// applicaiton has timed out
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(applicationDidTimeout:) name:kApplicationDidTimeoutNotification object:nil];
return YES;
}
-(void)applicationDidTimeout:(NSNotification *) notif
{
NSLog (#"time exceeded!!");
UIViewController *controller = [[UIStoryboard storyboardWithName:#"Main" bundle:NULL] instantiateViewControllerWithIdentifier:#"StoryboardViewController"];
UINavigationController * navigation = [[UINavigationController alloc]initWithRootViewController:controller];
[self.window setRootViewController:navigation];
navigation.delegate = self;
navigation.navigationBarHidden = YES;
if (controller) {
#try {
[navigation pushViewController:controller animated:NO];
} #catch (NSException * ex) {
//“Pushing the same view controller instance more than once is not supported”
//NSInvalidArgumentException
NSLog(#"Exception: [%#]:%#",[ex class], ex );
NSLog(#"ex.name:'%#'", ex.name);
NSLog(#"ex.reason:'%#'", ex.reason);
//Full error includes class pointer address so only care if it starts with this error
NSRange range = [ex.reason rangeOfString:#"Pushing the same view controller instance more than once is not supported"];
if ([ex.name isEqualToString:#"NSInvalidArgumentException"] &&
range.location != NSNotFound) {
//view controller already exists in the stack - just pop back to it
[navigation popToViewController:controller animated:NO];
} else {
NSLog(#"ERROR:UNHANDLED EXCEPTION TYPE:%#", ex);
}
} #finally {
//NSLog(#"finally");
}
} else {
NSLog(#"ERROR:pushViewController: viewController is nil");
}
[(TIMERUIApplication *)[UIApplication sharedApplication] resetIdleTimer];
}
#end
I'm assuming you've written the logic for the timer somewhere. Can you just invalidate the timer when you've popped back to the rootViewController?
Also instead of pushing a viewController onto the navigationViewController and handling the errors, you should check to see if the controller you're pushing is already in the stack like so:
if (![navigation.viewControllers containsObject:viewController] {
// push onto the stack
}
You could also check to see how many levels are currently in the navigationController by checking the count of the viewControllers array like so:
if ([navigation.viewControllers count] == 0) {
// I know we're on the main screen because no additional viewControllers have been added to the stack.
}
If you are not using modal controllers anywhere then the simplest solution would be
UINavigationController* nav = (UINavigationController*)self.window.rootViewController; // You could just save the nav as part of your app delegate
if (nav.viewControllers.count > 1){
[nav popToRootViewControllerAnimated:YES];
}
This is different then your current code because your main page will not be deleted and recreated every time the timer goes off
Okay I figured out how to do this. I was making this way too complicated.
To solve this I simply made a property and method in the app delegate class where I could set a scene name.
Then in each view controller header file I import the header file for the app delegate class and define a reference to it. Then in the load event for each view I simply set the scene name in the app delegate class using this line of code.
[myAppDelegate setSceneName:self.title];
Easy peasy!
I'm trying to use storyboard and get things working properly. I've added a a Container View to one of my existing views. When I try to add a reference to this in my view controller .h file (ctrl-drag), I get a IBOutlet UIView *containerView. How do I get a reference to the container view's view controller instead? I need the container view controller so I can set it's delegate to my view's controller so they can "talk" to each other.
I have my story board setup as:
And its referenced in my .h file as:
Notice in the .h that is is a UIView, not my InstallViewController for the view. How do I add a reference to the view controller? I need to be able to set its delegate.
There is another solution by specifying an identifier for the embed segue(s) and retrieve the corresponding view controllers in method prepareForSegue:
The advantage of this way is that you needn't rely on a specific order in which your child view controllers are added due to the fact that each child view controller is embedded via an unique segue identifier.
Update 2013-01-17 - Example
- (void) prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender
{
// -- Master View Controller
if ([segue.identifier isEqualToString:c_SegueIdEmbedMasterVC])
{
self.masterViewController = segue.destinationViewController;
// ...
}
// -- Detail View Controller
else if ([segue.identifier isEqualToString:c_SegueIdEmbedDetailVC])
{
self.detailViewController = segue.destinationViewController;
// ...
}
}
c_SegueIdEmbedMasterVC & c_SegueIdEmbedDetailVC are constants with the corresponding ID of the segue IDs defined in the storyboard.
When you add a container view the xcode calls the UIViewController method addChildViewController:
In your case, you can get the container ViewController looking for it on the SplashViewController's list of childViewControllers, something like this:
for (UIViewController *childViewController in [self childViewControllers])
{
if ([childViewController isKindOfClass:[InstallViewController class]])
{
//found container view controller
InstallViewController *installViewController = (InstallViewController *)childViewController;
//do something with your container view viewcontroller
break;
}
}
I had the same doubt yesterday :)
The answer of Vitor Franchi is correct but could be more performant and convenient. Especially when accessing the child view controller several times.
Create a readonly property
#interface MyViewController ()
#property (nonatomic, weak, readonly) InstallViewController *cachedInstallViewController;
#end
Then create a convenient getter method
- (InstallViewController *)installViewController
{
if (_cachedInstallViewController) return _cachedInstallViewController;
__block InstallViewController *blockInstallViewController = nil;
NSArray *childViewControllers = self.childViewControllers;
[childViewControllers enumerateObjectsUsingBlock:^(id childViewController, NSUInteger idx, BOOL *stop) {
if ([childViewController isMemberOfClass:InstallViewController.class])
{
blockInstallViewController = childViewController;
*stop = YES;
}
}];
_cachedInstallViewController = blockInstallViewController;
return _cachedInstallViewController;
}
From now on access the child view controller that way
[self.installViewController doSomething];
UIView* viewInsideOfContainer = installerView.subviews[0];
Will give you the UIView inside of the UIViewController that your controller UIView references. You can cast the subview to any type that inherits from UIView.
If the nib is loaded it will call addChildViewController as part of the initialisation process
so a performant solution could be also to overwrite
- (void)addChildViewController:(UIViewController *)childController
there you can catch your childController e.g. by comparing its Class and assign it to a property / ivar
-(void)addChildViewController:(UIViewController *)childController
{
[super addChildViewController:childController];
if([childController isKindOfClass:[InstallViewController class]])
{
self.installViewController = (InstallViewController *)childController;
}
}
This will save your from iterating trough the childViewControllers.