We have common views that we use in our application in many locations inside of UINavigationControllers. Occasionally the UINavigationControllers are inside of popover views. Now the views we put into the nav controllers modify their navigation controller's toolbar buttons and, in some cases, use custom buttons that we've created. We need to be able to figure out from the UIViewcontroller itself if the view is inside of a popoverview so we can display the correctly colored buttons.
We can easily get the Navigation controller reference from the UIViewController, using UIViewController.navigationController, but there doesn't seem to be anything for finding a UIPopoverController.
Does anyone have any good ideas for how to do this?
Thanks!
As Artem said we have UIPopoverPresentationController since iOS8. To determine if view is in popover you can use its .arrowDirection property for example.
Check it in viewWillApear() of presented view controller:
// get it from parent NavigationController
UIPopoverPresentationController* popoverPresentationVC = self.parentViewController.popoverPresentationController;
if (UIPopoverArrowDirectionUnknown > popoverPresentationVC.arrowDirection) {
// presented as popover
} else {
// presented as modal view controller (on iPhone)
}
Here's another solution; define a protocol (e.g. PopoverSensitiveController) that has only one method:
#import "Foundation/Foundation.h"
#protocol PopoverSensitiveController
-(void) setIsInPopover:(BOOL) inPopover;
#end
A view controller that wants to know if it is in a popover then defines a property isInPopover; for example:
#import
#import "PopoverSensitiveController.h"
#pragma mark -
#pragma mark Interface
#interface MyViewController : UIViewController {
}
#pragma mark -
#pragma mark Properties
#property (nonatomic) BOOL isInPopover;
#pragma mark -
#pragma mark Instance Methods
...other stuff...
#end
Finally, in the splitView delegate (the assumption is that your app uses a split view controller):
#import "MySplitViewControllerDelegate.h"
#import "SubstitutableDetailViewController.h"
#import "PopoverSensitiveController.h"
#pragma mark -
#pragma mark Implementation
#implementation MySplitViewControllerDelegate
#pragma mark -
#pragma mark UISplitViewControllerDelegate protocol methods
-(void) splitViewController:(UISplitViewController *) splitViewController willHideViewController:(UIViewController *) aViewController withBarButtonItem:(UIBarButtonItem *) barButtonItem forPopoverController:(UIPopoverController *) pc {
// Keep references to the popover controller and the popover button, and tell the detail view controller to show the button
popoverController = [pc retain];
popoverButtonItem = [barButtonItem retain];
if ([[splitViewController.viewControllers objectAtIndex:1] respondsToSelector:#selector(showRootPopoverButtonItem:)]) {
UIViewController *detailViewController = [splitViewController.viewControllers objectAtIndex:1];
[detailViewController showRootPopoverButtonItem:barButtonItem];
}
if ([[splitViewController.viewControllers objectAtIndex:1] respondsToSelector:#selector(showRootPopoverButtonItem:)]) {
UIViewController *detailViewController = [splitViewController.viewControllers objectAtIndex:1];
[detailViewController showRootPopoverButtonItem:barButtonItem];
}
// If the view controller wants to know, tell it that it is a popover
if ([aViewController respondsToSelector:#selector(setIsInPopover:)]) {
[(id) aViewController setIsInPopover:YES];
}
// Make sure the proper view controller is in the popover controller and the size is as requested
popoverController.contentViewController = aViewController;
popoverController.popoverContentSize = aViewController.contentSizeForViewInPopover;
}
-(void) splitViewController:(UISplitViewController *) splitViewController willShowViewController:(UIViewController *) aViewController invalidatingBarButtonItem:(UIBarButtonItem *) barButtonItem {
// Tell the detail view controller to hide the button.
if ([[splitViewController.viewControllers objectAtIndex:1] respondsToSelector:#selector(invalidateRootPopoverButtonItem:)]) {
UIViewController *detailViewController = [splitViewController.viewControllers objectAtIndex:1];
[detailViewController invalidateRootPopoverButtonItem:barButtonItem];
}
// If the view controller wants to know, tell it that it is not in a popover anymore
if ([aViewController respondsToSelector:#selector(setIsInPopover:)]) {
[(id) aViewController setIsInPopover:NO];
}
// Now clear out everything
[popoverController release];
popoverController = nil;
[popoverButtonItem release];
popoverButtonItem = nil;
}
-(void) setPopoverButtonForSplitViewController:(UISplitViewController *) splitViewController {
// Deal with the popover button
UIViewController *detailViewController = [splitViewController.viewControllers objectAtIndex:1];
[detailViewController showRootPopoverButtonItem:popoverButtonItem];
// If the view controller wants to know, tell it that it is a popover (initialize the controller properly)
if ([[splitViewController.viewControllers objectAtIndex:0] respondsToSelector:#selector(setIsInPopover:)]) {
[(id) [splitViewController.viewControllers objectAtIndex:0] setIsInPopover:YES];
}
}
Then where ever in the view controller you want to know if you are in a popover, simply use the isInPopover property.
In iOS8 you can use popoverPresentationController property of UIViewController to check if it is contained in a popover presentation controller. From documentation, it returns: "The nearest ancestor in the view controller hierarchy that is a popover presentation controller. (read-only)"
I was recently looking for a way to determine wether or not a view was being displayed in a popover. This is what I came up with:
UIView *v=theViewInQuestion;
for (;v.superview != nil; v=v.superview) {
if (!strcmp(object_getClassName(v), "UIPopoverView")) {
NSLog(#"\n\n\nIM IN A POPOVER!\n\n\n\n");
}
Basically you climb the view's superview tree looking to see if any of its superviews is a UIPopoverView. The one caveat here is that the class UIPopoverView is an undocumented private class. I'm relying on the fact that the class name won't change in the future. YMMV.
In your case:
theViewInQuestion = theViewControllerInQuestion.view;
I'd be interested to see if anyone else comes up with a better solution.
Modification of the accepted answer for iOS5.1 and newer:
for (UIView *v = self.view; v.superview != nil; v=v.superview) {
if ([v isKindOfClass:[NSClassFromString(#"_UIPopoverView") class]]) {
NSLog(#"\n\n\nIM IN A POPOVER!\n\n\n\n");
}
}
** NOTE **
See comments about the reliability of this code.
My approach for this: (available with iOS 8 or greater)
- (BOOL)isContainedInPopover
{
UIPopoverPresentationController* popoverPresentationVC = self.parentViewController.popoverPresentationController;
return (popoverPresentationVC != nil);
}
Parent view controller will be the navigation controller which if inside a popover, will have a non-nil popoverPresentationController property.
By working with SpareTime's code I came to this, which works as expected. Nice code, nice solution:
Using the standard UISplitViewController example.
/* MasterViewController.h */
#import "UIPopoverViewDelegate.h"
#interface masterViewController : UITableViewController <UIPopoverViewDelegate>
#property (nonatomic) BOOL isInPopover;
#end
/* MasterViewController.m */
#import "MasterViewController.h"
#implementation MasterViewController
#synthesize isInPopover = _isInPopover;
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (self.isInPopover)
{
// Code for appearing in popover
}
else
{
// Code for not appearing in popover
}
}
#end
/* DetailViewController.h */
#import "UIPopoverViewDelegate.h"
#interface detailViewController : UIViewController <UISplitViewControllerDelegate>
#end
/* DetailViewController.m */
#import "DetailViewController.h"
#implementation detailViewController
- (void)splitViewController:(UISplitViewController *)splitController willHideViewController:(UIViewController *)viewController withBarButtonItem:(UIBarButtonItem *)barButtonItem forPopoverController:(UIPopoverController *)popoverController
{
/* This method is called when transitioning to PORTRAIT orientation. */
UIViewController *hiddenViewController = [(UINavigationController *)viewController childViewControllers].lastObject;
if ([hiddenViewController respondsToSelector:#selector(setIsInPopover:)])
[(id <UIPopoverViewDelegate>)hiddenViewController setIsInPopover:YES];
}
- (void)splitViewController:(UISplitViewController *)splitController willShowViewController:(UIViewController *)viewController invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem
{
/* This method is called when transitioning to LANDSCAPE orientation. */
UIViewController *shownViewController = [(UINavigationController *)viewController childViewControllers].lastObject;
if ([shownViewController respondsToSelector:#selector(setIsInPopover:)])
[(id <UIPopoverViewDelegate>)shownViewController setIsInPopover:NO];
}
#end
/* UIPopoverViewDelegate.h */
#protocol UIPopoverViewDelegate
#required
-(void)setIsInPopover:(BOOL)inPopover;
#end
In case that someone else is still looking for a solution i came up with one good enough for me.
Just override this method
func presentationController(_ controller: UIPresentationController, viewControllerForAdaptivePresentationStyle style: UIModalPresentationStyle) -> UIViewController? {
(controller.presentedViewController as? YourViewControler).isPopover = false
return controller.presentedViewController
}
Here is an example of YourViewController
class AdvisorHomeFilterViewController: UIViewController {
// MARK: - Properties
var isPopover = true
}
If it is popover it will not call 'viewControllerForAdaptivePresentationStyle' method and it will stay true, in case it is not popover it will set it to false.
I wanted to put up a button in the view if the view wasn't displayed in a popover. I know the width of the popover because I just set it. So i can test whether I'm on an iPad and if the width of the frame is the same as what I set.
- (void)viewWillAppear:(BOOL)animated {
[self setContentSizeForViewInPopover:CGSizeMake(400, 500)];
NSInteger frameWidth = self.view.frame.size.width;
//Let you go back to the game if on an iPod.
if ( ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) && !(frameWidth == 400) ) { ---code to display a button --}
All these 'Exact Classname Matching Approaches' are very prone to fail and break at even the slightest changes Apple will make. Also doing one-char-vars and cryptic for-loops is not exactly a solution fitting to my style.
I use followingpiece of code:
- (BOOL) isInPopOver {
UIView *currentView = self.view;
while( currentView ) {
NSString *classNameOfCurrentView = NSStringFromClass([currentView class]);
NSLog( #"CLASS-DETECTED: %#", classNameOfCurrentView );
NSString *searchString = #"UIPopoverView";
if( [classNameOfCurrentView rangeOfString:searchString options:NSCaseInsensitiveSearch].location != NSNotFound ) {
return YES;
}
currentView = currentView.superview;
}
return NO;
}
All the solutions above seems a little bit complicated. I'm using a variable called isInPopover which I set to true if the view controller is presented in a popover. In the view controller in popoverControllerDidDismissPopover or in viewWillDisappear I set the boolean value to false. It does work and is very simple.
Since self.popoverPresentationController is created lazily in most recent iOS versions, one should check for nil-ness of self.popoverPresentationController.presentingViewController, if not nil this would mean self is currently presented in a popover.
Swift 4 version (function can be added in extension UIViewController):
func isInPopover() -> Bool {
guard UIDevice.current.userInterfaceIdiom == .pad else { return false }
var checkingVC: UIViewController? = self
repeat {
if checkingVC?.modalPresentationStyle == .popover {
return true
}
checkingVC = checkingVC?.parent
} while checkingVC != nil
return false
}
Related
Trying to achieve
When I tap on the tabbaritem say #2, it will called the method and reload the web view.
Issue
When I tap on the tabbaritem, the method is called but web view did not reload.
Did not load the web view
Question
If I called the method on the VC itself. I can manage to reload the web view. Only if I called it when the tabbaritem is tapped, it doesn't reload the web view.
Code
MyTabBarController.m
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
NSLog(#"controller class: %#", NSStringFromClass([viewController class]));
NSLog(#"controller title: %#", viewController.title);
if (viewController == [tabBarController.viewControllers objectAtIndex:2])
{
[(UINavigationController *)viewController popToRootViewControllerAnimated:YES];
tabBarController.delegate = self;
[[[Classes alloc] init] LoadClasses];
}else if (viewController == [tabBarController.viewControllers objectAtIndex:3]){
[(UINavigationController *)viewController popToRootViewControllerAnimated:YES];
tabBarController.moreNavigationController.delegate = self;
[[[Gym alloc] init] handleRefreshGym:nil];
}else{
[(UINavigationController *)viewController popToRootViewControllerAnimated:NO];
}
}
Classes.m
- (void)LoadClasses {
sURL = #"www.share-fitness.com/apps/class.asp?memCode=SF100012&dtpClass=13/09/2018&lang=EN&lat=37.785835&long=-122.406418&ver=1&plat=IOS"
NSLog(#"The URL To be loaded %#", sURL);
NSURL *url = [NSURL URLWithString:sURL];
sRefresh = sURL;
[[NSURLCache sharedURLCache] removeAllCachedResponses];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
[webView loadRequest:urlRequest];
[webView setDelegate:(id<UIWebViewDelegate>)self];
UIRefreshControl *refreshControl = [[UIRefreshControl alloc] init];
[refreshControl addTarget:self action:#selector(handleRefresh:) forControlEvents:UIControlEventValueChanged];
[webView.scrollView addSubview:refreshControl];
}
As I mentioned in my other reply Objective-C: How to properly set didSelectViewController method for TabBarController, so I can refresh the VC everytime it is tapped, I don't think it's good User Experience to be refreshing the view from the server every time the tab bar is selected (this will get very annoying for users to wait every time for the server to refresh the data)
That being said, the issue with the code you posted is that you're initializing a new instance of your classes in the TabBarControllerDelegate method so the method will be called on this new instance instead of on the one that's displaying/exists in your TabBarController's view controllers. Specifically these two lines are initializing the new instances:
[[[Classes alloc] init] LoadClasses];
[[[Gym alloc] init] handleRefreshGym:nil];
Instead you should be finding the instance that already exists, and calling the method on them.
I would recommend creating a ParentViewController with a public method along the lines of - (void)doStuffWhenTabBarControllerSelects; (just example naming to be clear what's it doing to you) then have each of the view controllers you'd like to have do something when they're selected be child classes of this parent (and have their own implementation of - (void)doStuffWhenTabBarControllerSelects;). This way in the TabBarController's delegate method, you can just find the appropriate instance of ParentViewController (associated with the view controller being selected) and call the - (void)doStuffWhenTabBarControllerSelects; method on it.
Here's an example of what I mean:
ParentViewController.h:
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
#interface ParentViewController : UIViewController
- (void)doStuffWhenTabBarControllerSelects;
#end
NS_ASSUME_NONNULL_END
ParentViewController.m:
#import "ParentViewController.h"
#interface ParentViewController ()
#end
#implementation ParentViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)doStuffWhenTabBarControllerSelects {
NSLog(#"Fallback implementation if this method isn't implemented by the child class");
}
#end
FirstViewController.h:
#import <UIKit/UIKit.h>
#import "ParentViewController.h"
#interface FirstViewController : ParentViewController
#end
FirstViewController.m:
#import "FirstViewController.h"
#interface FirstViewController ()
#end
#implementation FirstViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)doStuffWhenTabBarControllerSelects {
NSLog(#"I'm doing stuff on the %# when the tab bar controller delegate calls back to selection", NSStringFromClass([self class]));
}
#end
SecondViewController.h:
#import <UIKit/UIKit.h>
#import "ParentViewController.h"
#interface SecondViewController : ParentViewController
#end
SecondViewController.m:
#import "SecondViewController.h"
#interface SecondViewController ()
#end
#implementation SecondViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)doStuffWhenTabBarControllerSelects {
NSLog(#"I'm doing stuff on the %# when the tab bar controller delegate calls back to selection", NSStringFromClass([self class]));
}
#end
MyTabBarController.h:
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
#interface MyTabBarController : UITabBarController <UITabBarControllerDelegate>
#end
NS_ASSUME_NONNULL_END
MyTabBarController.m:
#import "MyTabBarController.h"
#import "ParentViewController.h"
#implementation MyTabBarController
- (void)viewDidLoad {
[super viewDidLoad];
self.delegate = self;
}
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
// since your view controllers are embedded in nav controllers, let's make sure we're getting a nav controller
if ([viewController isKindOfClass:[UINavigationController class]]) {
// we're expecting a nav controller so cast it to a nav here
UINavigationController *navController = (UINavigationController *)viewController;
// now grab the first view controller from that nav controller
UIViewController *firstViewControllerInNav = navController.viewControllers.firstObject;
// check to make sure it's what we're expecting (ParentViewController)
if ([firstViewControllerInNav isKindOfClass:[ParentViewController class]]) {
// cast it to our parent view controller class
ParentViewController *viewControllerToCallMethodOnAfterSelection = (ParentViewController *)firstViewControllerInNav;
[viewControllerToCallMethodOnAfterSelection doStuffWhenTabBarControllerSelects];
}
}
}
#end
Then when you select between the two tabs you'll this is the output:
I'm doing stuff on the FirstViewController when the tab bar controller delegate calls back to selection
I'm doing stuff on the SecondViewController when the tab bar controller delegate calls back to selection
I'd recommend doing some additional research/reading of the documentation:
There's a good amount of beginner information here: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/DefiningClasses/DefiningClasses.html#//apple_ref/doc/uid/TP40011210-CH3-SW1
UITabBarController: https://developer.apple.com/documentation/uikit/uitabbarcontroller?language=objc
UITabBarControllerDelegate:
https://developer.apple.com/documentation/uikit/uitabbarcontrollerdelegate?language=objc
One other helpful hint is that within Xcode you can hold down on the option key and click on something to show a quicklook into the explanation/documentation
You can also right click on something and "Jump To Definition". The majority of Apple's implementations will will have additional information in the header.
Here's the example of what's in the header of UITabBarController:
/*!
UITabBarController manages a button bar and transition view, for an application with multiple top-level modes.
To use in your application, add its view to the view hierarchy, then add top-level view controllers in order.
Most clients will not need to subclass UITabBarController.
If more than five view controllers are added to a tab bar controller, only the first four will display.
The rest will be accessible under an automatically generated More item.
UITabBarController is rotatable if all of its view controllers are rotatable.
*/
NS_CLASS_AVAILABLE_IOS(2_0) #interface UITabBarController : UIViewController <UITabBarDelegate, NSCoding>
As well as under the Help Menu there's "Developer Documentation" (CMD + SHIFT + 0) which has a multitude of useful information.
I'm trying to disable screen rotation in just one ViewController. I'm using this to change screen orientation to portrait:
-(void)viewDidAppear:(BOOL)animated{
NSNumber *value = [NSNumber numberWithInt:UIInterfaceOrientationPortrait];
[[UIDevice currentDevice] setValue:value forKey:#"orientation"];
}
and I'm disabling rotation like this:
- (BOOL)shouldAutorotate{
return NO;
}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskPortrait;
}
-(NSUInteger)navigationControllerSupportedInterfaceOrientations:(UINavigationController *)navigationController {
return navigationController.topViewController.supportedInterfaceOrientations;
}
but it's not working. It rotates screen to portrait but it does't lock it, if I turn device it changes screen orientation.
You can try this code:
-(BOOL)shouldAutorotate
{
return NO;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
return NO;
}
- (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation
{
return UIInterfaceOrientationPortrait;
}
The above code will only work with UIViewControllers not UINavigationController stacks. If you are using a UINavigationController you should do the following:
Solution 1:
Add to AppDelegate.h a variable: #property (nonatomic , assign) bool blockRotation;
Add to AppDelegate.m function:
- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window
{
if (self.blockRotation) {
return UIInterfaceOrientationMaskPortrait;
}
return UIInterfaceOrientationMaskAll;
}
In controller want disable add this code:
#import "AppDelegate.h"
//Put to `viewDidload`
AppDelegate* shared=[UIApplication sharedApplication].delegate;
shared.blockRotation=YES;
Solution 2: you can follow this answer: Hanling orientation
If you want to temporarily disable automatic rotation, avoid manipulating the orientation masks to do this. Instead, override the shouldAutorotate method on the initial view controller. This method is called before performing any autorotation. If it returns NO, then the rotation is suppressed.
So you need to subclass 'UINavigationController', implement shouldAutorotate and use your navigation controller class in your storyboard.
- (BOOL)shouldAutorotate
{
id currentViewController = self.topViewController;
if ([currentViewController isKindOfClass:[DetailViewController class]])
return NO;
return YES;
}
How do I get a reference to the UIViewController of a touched view?
I am using a UIPanGestureRecognizer on the view of a UIViewController. Here's how I initialize it:
TaskUIViewController *thisTaskController = [[TaskUIViewController alloc]init];
[[self view]addSubview:[thisTaskController view]];
UIPanGestureRecognizer *panRec = [[UIPanGestureRecognizer alloc]initWithTarget:self action:#selector(handlePan:)];
[[thisTaskController view] addGestureRecognizer:panRec];
In the tiggered action triggered using the gesture recognizer I am able to get the view from the parameter using recognizer.view
- (IBAction)handlePan:(UIPanGestureRecognizer *)recognizer {
UIView *touchedView = [[UIView alloc]init];
touchedView = (UIView*)[recognizer view];
...
}
However what I really need is the underlying UIViewController of the view touched. How can I get a reference to the UIViewController that contains this view instead of only the UIView?
I would say that it is more a design issue than just getting a reference. So I would follow several simple advises:
Owner should catch events from its view. I.e. TaskUIViewController sould be a target to UIPanGestureRecognizer which you added to its view.
If a controller has a sub-controller and waits from its sub-controller some responses - implement this as delegate.
You have memory leak in your "handlePan:" method.
Here is a skeleton to solve your issue:
#protocol CallbackFromMySubcontroller <NSObject>
- (void)calbackFromTaskUIViewControllerOnPanGesture:(UIViewController*)fromController;
#end
#interface OwnerController : UIViewController <CallbackFromMySubcontroller>
#end
#implementation OwnerController
- (id)init
{
...
TaskUIViewController *thisTaskController = [[TaskUIViewController alloc] init];
...
}
- (void)viewDidLoad
{
...
[self.view addSubview:thisTaskController.view];
...
}
- (void)calbackFromTaskUIViewControllerOnPanGesture:(UIViewController*)fromController
{
NSLog(#"Yahoo. I got an event from my subController's view");
}
#end
#interface TaskUIViewController : UIViewController {
id <CallbackFromMySubcontroller> delegate;
}
#end
#implementation TaskUIViewController
- (id)initWithOwner:(id<CallbackFromMySubcontroller>)owner
{
...
delegate = owner;
...
}
- (void)viewDidLoad
{
UIPanGestureRecognizer *panRec = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
[self.view addGestureRecognizer:panRec];
[panRec release];
}
- (void)handlePan:(UIPanGestureRecognizer *)recognizer {
...
[delegate calbackFromTaskUIViewControllerOnPanGesture:self];
...
}
#end
[touchedView nextResponder] will return the UIViewController object that manages touchedView (if it has one) or touchedView's superview (if it doesn’t have a UIViewController object that manages it).
For more information, see the UIResponder Class Reference. (UIViewController and UIView are subclasses of UIResponder.)
In your case, since you happen to know that touchedView is your viewController's view (and not, for instance, a subview of your viewController's view), you can just use:
TaskUIViewController *touchedController = (TaskUIViewController *)[touchedView nextResponder];
In the more general case, you could work up the responder chain until you find an object of kind UIViewController:
id aNextResponder = [touchedView nextResponder];
while (aNextResponder != nil)
{
if ([aNextResponder isKindOfClass:[UIViewController class]])
{
// we have found the viewController that manages touchedView,
// so we break out of the while loop:
break;
}
else
{
// we have yet to find the managing viewController,
// so we examine the next responder in the responder chain
aNextResponder = [aNextResponder nextResponder];
}
}
// outside the while loop. at this point aNextResponder points to
// touchedView's managing viewController (or nil if it doesn't have one).
UIViewController *eureka = (UIViewController *)aNextResponder;
I did a lot of research on UISplitView and was not able to find a way to control a Split View when the Master and the Detail has a view that changes.
Then I found a way to manage it with a singleton class that is the delegate.
My problem is that i'm not sure if it's the right way to go. I'm concerned about reusability and memory managment. Also I have a feeling that it's aginst Apple guidelines to make delegates in singletons.
This is what I have (and it's actually working):
// SharedSplitViewDelegate.h
/* In the detail view controllers:
// in the initial detail view controller
- (void)awakeFromNib
{
[super awakeFromNib];
// needs to be here, otherwise if it's booted in portrait the button is not set
self.splitViewController.delegate = [SharedSplitViewDelegate initSharedSplitViewDelegate];
}
// shared between all detail view controllers
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
SharedSplitViewDelegate *rotationHandler = [SharedSplitViewDelegate initSharedSplitViewDelegate];
[self.toolbar setItems:[rotationHandler processButtonArray:self.toolbar.items] animated:YES];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return YES;
}
*/
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#interface SharedSplitViewDelegate : NSObject <UISplitViewControllerDelegate>
+ (id)initSharedSplitViewDelegate; // returns the singleton class instance
- (NSArray *)processButtonArray:(NSArray *)array; // Adds and removes the button from the toolbar array. Returns the modified array.
#end
Now the implementation:
// SharedSplitViewDelegate.m
#import "SharedSplitViewDelegate.h"
#interface SharedSplitViewDelegate()
#property (nonatomic, strong) UIBarButtonItem *button;
#property (nonatomic, strong) UIBarButtonItem *cachedButton;
#end
#implementation SharedSplitViewDelegate
#synthesize button = _button;
#synthesize cachedButton = _cachedButton;
#pragma mark - Singleton class definition
static id sharedSplitViewDelegate = nil;
+ (void)initialize
{
if (self == [SharedSplitViewDelegate class]) {
sharedSplitViewDelegate = [[self alloc] init];
}
}
+ (id)initSharedSplitViewDelegate {
return sharedSplitViewDelegate;
}
#pragma mark - Split view delegate methods
- (BOOL)splitViewController:(UISplitViewController *)svc
shouldHideViewController:(UIViewController *)vc
inOrientation:(UIInterfaceOrientation)orientation
{
if (orientation == UIInterfaceOrientationLandscapeLeft || orientation == UIInterfaceOrientationLandscapeRight) {
return NO;
} else {
return YES;
}
}
- (void)splitViewController:(UISplitViewController *)svc
willHideViewController:(UIViewController *)aViewController
withBarButtonItem:(UIBarButtonItem *)barButtonItem
forPopoverController:(UIPopoverController *)pc
{
barButtonItem.title = #"Browse";
self.button = barButtonItem;
}
- (void)splitViewController:(UISplitViewController *)svc
willShowViewController:(UIViewController *)aViewController
invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem
{
self.button = nil;
}
#pragma mark - Utility methods
- (void)setButton:(UIBarButtonItem *)button
{
if (button != _button) {
_button = button;
}
if (button != nil) {
self.cachedButton = button;
}
}
- (NSArray *)processButtonArray:(NSArray *)array
{
NSMutableArray *processedArray = [array mutableCopy];
if (self.button != nil && ![processedArray containsObject:self.button]) {
[processedArray insertObject:self.button atIndex:0];
} else if (self.button == nil && [processedArray containsObject:self.cachedButton]) {
[processedArray removeObjectAtIndex:0];
}
return [processedArray copy];
}
#end
This code is free to use and modify for everyone that would find it viable in their project :).
I'm new to StackOverflow (even though I've lurked for a couple months without an account) so every critique is warmly welcomed.
IMHO, every design pattern, architecture, is 'good' if it fits the 'problem' you have to solve (and fits your personal preferences for code organisation)
What's your problem ?
Why do you need this object ?
Could this singleton UISplitViewDelegate be your
UIApplicationDelegate ? (Keep it Simple ;-)
further discussion =>
If you UIApplicationDelegate is a mess, rather than creating sub-object, a scheme I've been using recently to organize my code : use categories and class extensions
Example :
If my ViewController class handles complex tasks whose code can be separated in groups.
let's say :
sound
core data
location-aware,
I create a category for each of these
UIViewController+soundManager
UIViewController+dataProvider
UIViewController+locationManager.
(in same file with several #interface #implementation, or in different files => i use several files)
Then along with each category I write a class-extension for properties this particular category needs.
Last time I solved this by subclassing the UISplitViewController and used it as his own delegate.
I have a UINavigationController and I would like the view of every view controller that is popped onto the stack to have a common padding/margin (e.g. 25 pixels on all sides). What is the best way to accomplish this?
I originally thought that I could implement UINavigationControllerDelegate and inside the navigationController:didShowViewController:animated or navigationController:willShowViewController:animated methods, simply change the frame of the view controller that was about to be displayed. This does not seem to have an effect though.
I tried to do the same thing inside the view controller's viewDidAppear and viewWillAppear methods, but this also did not work. Ideally, I don't want to put any logic in the controllers anyway, as they may not always be used inside a navigation controller.
One last idea that I haven't tried yet is to create a "wrapper" UIViewController that would actually get pushed onto this stack. This wrapper would add the real view controller's view as a subview with a frame that would provide the desired margin. The downside here is that I would need to subclass UINavigationController and override pushViewController:animated, where the wrapper would be initialized and pushed. Apple's documentation indicates that UINavigationController is not meant to be subclassed.
Thanks in advance.
I solved this by putting a "wrapper" UIView around the UIViewController's view instead of the UIViewController itself. The wrapper view then pads the subview by setting the subview's frame in the layoutSubviews method.
I've attached the code I used for convenience. To use, replace your UINavigationController with the PaddedNavigationController, and set the PaddedNavigationController's insets property.
PaddedNavigationController.h:
#import <Foundation/Foundation.h>
#interface PaddedNavigationController : UINavigationController
{
UIEdgeInsets _insets;
}
#property (nonatomic, assign) UIEdgeInsets insets;
#end
PaddedNavigationController.m:
#import "PaddedNavigationController.h"
#interface PaddedView : UIView
{
UIView *_view;
UIEdgeInsets _insets;
}
#property (nonatomic, assign) UIEdgeInsets insets;
+ (PaddedView *) wrapView:(UIView *)view withInsets:(UIEdgeInsets)insets;
- (id) initWithView:(UIView *)view insets:(UIEdgeInsets)insets;
#end
#implementation PaddedNavigationController
#synthesize insets = _insets;
- (void) pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
//check if the UIViewController's view has already been wrapped by the PaddedView; don't want to wrap it twice
if(![viewController.view isKindOfClass:[PaddedView class]])
{
viewController.view = [PaddedView wrapView:viewController.view withInsets:self.insets];
}
[super pushViewController:viewController animated:animated];
}
- (void) setInsets:(UIEdgeInsets)insets
{
_insets = insets;
//loop through this navigation controller's view controllers and set the new insets on any PaddedViews
for(UIViewController *viewController in self.viewControllers)
{
if([viewController.view isKindOfClass:[PaddedView class]])
{
PaddedView *padded = (PaddedView *)viewController.view;
padded.insets = insets;
}
}
}
#end
#implementation PaddedView
#synthesize insets = _insets;
+ (PaddedView *) wrapView:(UIView *)view withInsets:(UIEdgeInsets)insets
{
return [[[PaddedView alloc] initWithView:view insets:insets] autorelease];
}
- (id) initWithView:(UIView *)view insets:(UIEdgeInsets)insets
{
if(self = [super initWithFrame:view.frame])
{
_insets = insets;
self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_view = [view retain];
[self addSubview:view];
}
return self;
}
- (void) dealloc
{
[_view release];
[super dealloc];
}
- (void) layoutSubviews
{
//apply the insets to the subview
_view.frame = CGRectMake(self.insets.left, self.insets.top, self.frame.size.width - self.insets.left - self.insets.right, self.frame.size.height - self.insets.top - self.insets.bottom);
}
- (void) setInsets:(UIEdgeInsets)insets
{
_insets = insets;
//we need to re-layout the subviews as the insets have changed
[self layoutSubviews];
}
#end