Get to UIViewController from UIView? - ios

Is there a built-in way to get from a UIView to its UIViewController? I know you can get from UIViewController to its UIView via [self view] but I was wondering if there is a reverse reference?

Using the example posted by Brock, I modified it so that it is a category of UIView instead UIViewController and made it recursive so that any subview can (hopefully) find the parent UIViewController.
#interface UIView (FindUIViewController)
- (UIViewController *) firstAvailableUIViewController;
#end
#implementation UIView (FindUIViewController)
- (UIViewController *) firstAvailableUIViewController {
UIResponder *responder = [self nextResponder];
while (responder != nil) {
if ([responder isKindOfClass:[UIViewController class]]) {
return (UIViewController *)responder;
}
responder = [responder nextResponder];
}
return nil;
}
#end
To use this code, add it into an new class file (I named mine "UIKitCategories") and remove the class data... copy the #interface into the header, and the #implementation into the .m file. Then in your project, #import "UIKitCategories.h" and use within the UIView code:
// from a UIView subclass... returns nil if UIViewController not available
UIViewController * myController = [self firstAvailableUIViewController];

UIView is a subclass of UIResponder. UIResponder lays out the method -nextResponder with an implementation that returns nil. UIView overrides this method, as documented in UIResponder (for some reason instead of in UIView) as follows: if the view has a view controller, it is returned by -nextResponder. If there is no view controller, the method will return the superview.
Add this to your project and you're ready to roll.
#interface UIView (APIFix)
- (UIViewController *)viewController;
#end
#implementation UIView (APIFix)
- (UIViewController *)viewController {
if ([self.nextResponder isKindOfClass:UIViewController.class])
return (UIViewController *)self.nextResponder;
else
return nil;
}
#end
Now UIView has a working method for returning the view controller.

Since this has been the accepted answer for a long time, I feel I need to rectify it with a better answer.
Some comments on the need:
Your view should not need to access the view controller directly.
The view should instead be independent of the view controller, and be able to work in different contexts.
Should you need the view to interface in a way with the view controller, the recommended way, and what Apple does across Cocoa is to use the delegate pattern.
An example of how to implement it follows:
#protocol MyViewDelegate < NSObject >
- (void)viewActionHappened;
#end
#interface MyView : UIView
#property (nonatomic, assign) MyViewDelegate delegate;
#end
#interface MyViewController < MyViewDelegate >
#end
The view interfaces with its delegate (as UITableView does, for instance) and it doesn't care if its implemented in the view controller or in any other class that you end up using.
My original answer follows: I don't recommend this, neither the rest of the answers where direct access to the view controller is achieved
There is no built-in way to do it. While you can get around it by adding a IBOutlet on the UIView and connecting these in Interface Builder, this is not recommended. The view should not know about the view controller. Instead, you should do as #Phil M suggests and create a protocol to be used as the delegate.

I would suggest a more lightweight approach for traversing the complete responder chain without having to add a category on UIView:
#implementation MyUIViewSubclass
- (UIViewController *)viewController {
UIResponder *responder = self;
while (![responder isKindOfClass:[UIViewController class]]) {
responder = [responder nextResponder];
if (nil == responder) {
break;
}
}
return (UIViewController *)responder;
}
#end

Combining several already given answers, I'm shipping on it as well with my implementation:
#implementation UIView (AppNameAdditions)
- (UIViewController *)appName_viewController {
/// Finds the view's view controller.
// Take the view controller class object here and avoid sending the same message iteratively unnecessarily.
Class vcc = [UIViewController class];
// Traverse responder chain. Return first found view controller, which will be the view's view controller.
UIResponder *responder = self;
while ((responder = [responder nextResponder]))
if ([responder isKindOfClass: vcc])
return (UIViewController *)responder;
// If the view controller isn't found, return nil.
return nil;
}
#end
The category is part of my ARC-enabled static library that I ship on every application I create. It's been tested several times and I didn't find any problems or leaks.
P.S.: You don't need to use a category like I did if the concerned view is a subclass of yours. In the latter case, just put the method in your subclass and you're good to go.

I modified de answer so I can pass any view, button, label etc. to get it's parent UIViewController. Here is my code.
+(UIViewController *)viewController:(id)view {
UIResponder *responder = view;
while (![responder isKindOfClass:[UIViewController class]]) {
responder = [responder nextResponder];
if (nil == responder) {
break;
}
}
return (UIViewController *)responder;
}
Edit Swift 3 Version
class func viewController(_ view: UIView) -> UIViewController {
var responder: UIResponder? = view
while !(responder is UIViewController) {
responder = responder?.next
if nil == responder {
break
}
}
return (responder as? UIViewController)!
}
Edit 2:- Swift Extention
extension UIView
{
//Get Parent View Controller from any view
func parentViewController() -> UIViewController {
var responder: UIResponder? = self
while !(responder is UIViewController) {
responder = responder?.next
if nil == responder {
break
}
}
return (responder as? UIViewController)!
}
}

Even though this can technically be solved as pgb recommends, IMHO, this is a design flaw. The view should not need to be aware of the controller.

Don't forget that you can get access to the root view controller for the window that the view is a subview of. From there, if you are e.g. using a navigation view controller and want to push a new view onto it:
[[[[self window] rootViewController] navigationController] pushViewController:newController animated:YES];
You will need to set up the rootViewController property of the window properly first, however. Do this when you first create the controller e.g. in your app delegate:
-(void) applicationDidFinishLaunching:(UIApplication *)application {
window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
RootViewController *controller = [[YourRootViewController] alloc] init];
[window setRootViewController: controller];
navigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
[controller release];
[window addSubview:[[self navigationController] view]];
[window makeKeyAndVisible];
}

I stumbled upon a situation where I have a small component I want to reuse, and added some code in a reusable view itself(it's really not much more than a button that opens a PopoverController).
While this works fine in the iPad (the UIPopoverController presents itself, therefor needs no reference to a UIViewController), getting the same code to work means suddenly referencing your presentViewController from your UIViewController. Kinda inconsistent right?
Like mentioned before, it's not the best approach to have logic in your UIView. But it felt really useless to wrap the few lines of code needed in a separate controller.
Either way, here's a swift solution, which adds a new property to any UIView:
extension UIView {
var viewController: UIViewController? {
var responder: UIResponder? = self
while responder != nil {
if let responder = responder as? UIViewController {
return responder
}
responder = responder?.nextResponder()
}
return nil
}
}

While these answers are technically correct, including Ushox, I think the approved way is to implement a new protocol or re-use an existing one. A protocol insulates the observer from the observed, sort of like putting a mail slot in between them. In effect, that is what Gabriel does via the pushViewController method invocation; the view "knows" that it is proper protocol to politely ask your navigationController to push a view, since the viewController conforms to the navigationController protocol. While you can create your own protocol, just using Gabriel's example and re-using the UINavigationController protocol is just fine.

I don't think it's "bad" idea to find out who is the view controller for some cases. What could be a bad idea is to save the reference to this controller as it could change just as superviews change.
In my case I have a getter that traverses the responder chain.
//.h
#property (nonatomic, readonly) UIViewController * viewController;
//.m
- (UIViewController *)viewController
{
for (UIResponder * nextResponder = self.nextResponder;
nextResponder;
nextResponder = nextResponder.nextResponder)
{
if ([nextResponder isKindOfClass:[UIViewController class]])
return (UIViewController *)nextResponder;
}
// Not found
NSLog(#"%# doesn't seem to have a viewController". self);
return nil;
}

Swift 4
(more concise than the other answers)
fileprivate extension UIView {
var firstViewController: UIViewController? {
let firstViewController = sequence(first: self, next: { $0.next }).first(where: { $0 is UIViewController })
return firstViewController as? UIViewController
}
}
My use case for which I need to access the view first UIViewController: I have an object that wraps around AVPlayer / AVPlayerViewController and I want to provide a simple show(in view: UIView) method that will embed AVPlayerViewController into view. For that, I need to access view's UIViewController.

Two solutions as of Swift 5.2:
More on the functional side
No need for the return keyword now 🤓
Solution 1:
extension UIView {
var parentViewController: UIViewController? {
sequence(first: self) { $0.next }
.first(where: { $0 is UIViewController })
.flatMap { $0 as? UIViewController }
}
}
Solution 2:
extension UIView {
var parentViewController: UIViewController? {
sequence(first: self) { $0.next }
.compactMap{ $0 as? UIViewController }
.first
}
}
This solution requires iterating through each responder first, so may not be the most performant.

The simplest do while loop for finding the viewController.
-(UIViewController*)viewController
{
UIResponder *nextResponder = self;
do
{
nextResponder = [nextResponder nextResponder];
if ([nextResponder isKindOfClass:[UIViewController class]])
return (UIViewController*)nextResponder;
} while (nextResponder != nil);
return nil;
}

This doesn't answer the question directly, but rather makes an assumption about the intent of the question.
If you have a view and in that view you need to call a method on another object, like say the view controller, you can use the NSNotificationCenter instead.
First create your notification string in a header file
#define SLCopyStringNotification #"ShaoloCopyStringNotification"
In your view call postNotificationName:
- (IBAction) copyString:(id)sender
{
[[NSNotificationCenter defaultCenter] postNotificationName:SLCopyStringNotification object:nil];
}
Then in your view controller you add an observer. I do this in viewDidLoad
- (void)viewDidLoad
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(copyString:)
name:SLCopyStringNotification
object:nil];
}
Now (also in the same view controller) implement your method copyString: as depicted in the #selector above.
- (IBAction) copyString:(id)sender
{
CalculatorResult* result = (CalculatorResult*)[[PercentCalculator sharedInstance].arrayTableDS objectAtIndex:([self.viewTableResults indexPathForSelectedRow].row)];
UIPasteboard *gpBoard = [UIPasteboard generalPasteboard];
[gpBoard setString:result.stringResult];
}
I'm not saying this is the right way to do this, it just seems cleaner than running up the first responder chain. I used this code to implement a UIMenuController on a UITableView and pass the event back up to the UIViewController so I can do something with the data.

It's surely a bad idea and a wrong design, but I'm sure we can all enjoy a Swift solution of the best answer proposed by #Phil_M:
static func firstAvailableUIViewController(fromResponder responder: UIResponder) -> UIViewController? {
func traverseResponderChainForUIViewController(responder: UIResponder) -> UIViewController? {
if let nextResponder = responder.nextResponder() {
if let nextResp = nextResponder as? UIViewController {
return nextResp
} else {
return traverseResponderChainForUIViewController(nextResponder)
}
}
return nil
}
return traverseResponderChainForUIViewController(responder)
}
If your intention is to do simple things, as showing a modal dialog or tracking data, that doesn't justify the use of a protocol. I personally store this function in an utility object, you can use it from anything that implement the UIResponder protocol as:
if let viewController = MyUtilityClass.firstAvailableUIViewController(self) {}
All credit to #Phil_M

Maybe I'm late here. But in this situation I don't like category (pollution). I love this way:
#define UIViewParentController(__view) ({ \
UIResponder *__responder = __view; \
while ([__responder isKindOfClass:[UIView class]]) \
__responder = [__responder nextResponder]; \
(UIViewController *)__responder; \
})

Swiftier solution
extension UIView {
var parentViewController: UIViewController? {
for responder in sequence(first: self, next: { $0.next }) {
if let viewController = responder as? UIViewController {
return viewController
}
}
return nil
}
}

Swift 4 version
extension UIView {
var parentViewController: UIViewController? {
var parentResponder: UIResponder? = self
while parentResponder != nil {
parentResponder = parentResponder!.next
if let viewController = parentResponder as? UIViewController {
return viewController
}
}
return nil
}
Usage example
if let parent = self.view.parentViewController{
}

Updated version for swift 4 : Thanks for #Phil_M and #paul-slm
static func firstAvailableUIViewController(fromResponder responder: UIResponder) -> UIViewController? {
func traverseResponderChainForUIViewController(responder: UIResponder) -> UIViewController? {
if let nextResponder = responder.next {
if let nextResp = nextResponder as? UIViewController {
return nextResp
} else {
return traverseResponderChainForUIViewController(responder: nextResponder)
}
}
return nil
}
return traverseResponderChainForUIViewController(responder: responder)
}

To Phil's answer:
In line: id nextResponder = [self nextResponder]; if self(UIView) is not a subview of ViewController's view, if you know hierarchy of self(UIView) you can use also: id nextResponder = [[self superview] nextResponder];...

If you aren't going to upload this to the App Store, you can also use a private method of UIView.
#interface UIView(Private)
- (UIViewController *)_viewControllerForAncestor;
#end
// Later in the code
UIViewController *vc = [myView _viewControllerForAncestor];

var parentViewController: UIViewController? {
let s = sequence(first: self) { $0.next }
return s.compactMap { $0 as? UIViewController }.first
}

My solution would probably be considered kind of bogus but I had a similar situation as mayoneez (I wanted to switch views in response to a gesture in an EAGLView), and I got the EAGL's view controller this way:
EAGLViewController *vc = ((EAGLAppDelegate*)[[UIApplication sharedApplication] delegate]).viewController;

I think there is a case when the observed needs to inform the observer.
I see a similar problem where the UIView in a UIViewController is responding to a situation and it needs to first tell its parent view controller to hide the back button and then upon completion tell the parent view controller that it needs to pop itself off the stack.
I have been trying this with delegates with no success.
I don't understand why this should be a bad idea?

Another easy way is to have your own view class and add a property of the view controller in the view class. Usually the view controller creates the view and that is where the controller can set itself to the property. Basically it is instead of searching around (with a bit of hacking) for the controller, having the controller to set itself to the view - this is simple but makes sense because it is the controller that "controls" the view.

To get the controller of a given view, one can use UIFirstResponder chain.
customView.target(forAction: Selector("viewDidLoad"), withSender: nil)

If your rootViewController is UINavigationViewController, which was set up in AppDelegate class, then
+ (UIViewController *) getNearestViewController:(Class) c {
NSArray *arrVc = [[[[UIApplication sharedApplication] keyWindow] rootViewController] childViewControllers];
for (UIViewController *v in arrVc)
{
if ([v isKindOfClass:c])
{
return v;
}
}
return nil;}
Where c required view controllers class.
USAGE:
RequiredViewController* rvc = [Utilities getNearestViewController:[RequiredViewController class]];

There is no way.
What I do is pass the UIViewController pointer to the UIView (or an appropriate inheritance). I'm sorry I can't help with the IB approach to the problem because I don't believe in IB.
To answer the first commenter: sometimes you do need to know who called you because it determines what you can do. For example with a database you might have read access only or read/write ...

Related

In my viewDidAppear, how do I know when it's being unwound by a child?

When my child performs an unwind segue, my controller's viewDidAppear gets called.
In this method (and this method alone, I need to know whether it was from an unwind or not)
Note: the child is unwinding to the very first view controller, so this is an intermediate view controller, not the true root.
You should be able to use the following to detect in each controller if the exposure of the view controller was as a result of being pushed/presented, or as a result of being exposed as a result of pop/dismiss/unwind.
This may or may be enough for your needs.
- (void) viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
// Handle controller being exposed from push/present or pop/dismiss
if (self.isMovingToParentViewController || self.isBeingPresented){
// Controller is being pushed on or presented.
}
else{
// Controller is being shown as result of pop/dismiss/unwind.
}
}
If you want to know that viewDidAppear was called because of an unwind segue as being different from a conventional pop/dismiss being called, then you need to add some code to detect that an unwind happened. To do this you could do the following:
For any intermediate controller you want to detect purely an unwind in, add a property of the form:
/** BOOL property which when TRUE indicates an unwind occured. */
#property BOOL unwindSeguePerformed;
Then override the unwind segue method canPerformUnwindSegueAction:fromViewController:withSender: method as follows:
- (BOOL)canPerformUnwindSegueAction:(SEL)action
fromViewController:(UIViewController *)fromViewController
withSender:(id)sender{
// Set the flag indicating an unwind segue was requested and then return
// that we are not interested in performing the unwind action.
self.unwindSeguePerformed = TRUE;
// We are not interested in performing it, so return NO. The system will
// then continue to look backwards through the view controllers for the
// controller that will handle it.
return NO;
}
Now you have a flag to detect an unwind and a means to detect the unwind just before it happens. Then adjust the viewDidAppear method to include this flag.
- (void) viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
// Handle controller being exposed from push/present or pop/dismiss
// or an unwind
if (self.isMovingToParentViewController || self.isBeingPresented){
// Controller is being pushed on or presented.
// Initialize the unwind segue tracking flag.
self.unwindSeguePerformed = FALSE;
}
else if (self.unwindSeguePerformed){
// Controller is being shown as a result of an unwind segue
}
else{
// Controller is being shown as result of pop/dismiss.
}
}
Hopefully this meets your requirement.
For docs on handling the unwind segue chain see: https://developer.apple.com/library/ios/technotes/tn2298/_index.html
Here is a simple category on UIViewController that you can use to track whether your presented view controller is in the midst of an unwind segue. I suppose it could be flushed out more but I believe this much works for your case.
To use it you need to register the unwind segue from your unwind action method on the destination view controller:
- (IBAction) prepareForUnwind:(UIStoryboardSegue *)segue
{
[self ts_registerUnwindSegue: segue];
}
That's it. From your intermediate view controller, you can test if you are in the midst of an unwind segue:
- (void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear: animated];
BOOL unwinding = [self ts_isUnwinding];
NSLog( #"%#:%#, unwinding: %#", self.title, NSStringFromSelector(_cmd), unwinding ? #"YES" : #"NO" );
}
There's no need to clean anything up; the segue will self-deregister when it ends.
Here's the full category:
#interface UIViewController (unwinding)
- (void) ts_registerUnwindSegue: (UIStoryboardSegue*) segue;
- (BOOL) ts_isUnwinding;
#end
static NSMapTable* g_viewControllerSegues;
#implementation UIViewController (unwinding)
- (void) ts_registerUnwindSegue: (UIStoryboardSegue*) segue
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
g_viewControllerSegues = [NSMapTable weakToWeakObjectsMapTable];
});
for ( UIViewController* vc = segue.sourceViewController ; vc != nil ; vc = vc.presentingViewController )
{
[g_viewControllerSegues setObject: segue forKey: vc];
}
}
- (BOOL) ts_isUnwinding
{
return [g_viewControllerSegues objectForKey: [self ts_topMostParentViewController]] != nil;
}
- (UIViewController *)ts_topMostParentViewController {
UIViewController *viewController = self;
while (viewController.parentViewController) {
viewController = viewController.parentViewController;
}
return viewController;
}
#end
Your question was really interesting to me, because I never used IB and segues before (don't judge me for that) and wanted to learn something new. As you described in your comments:
viewDidAppear will be called on B when C rewinds to A
So I come up with an easy custom solution to this:
protocol ViewControllerSingletonDelegate: class {
func viewControllerWillUnwind(viewcontroller: UIViewController, toViewController: UIViewController)
}
class ViewControllerSingleton {
static let sharedInstance = ViewControllerSingleton()
private var delegates: [ViewControllerSingletonDelegate] = []
func addDelegate(delegate: ViewControllerSingletonDelegate) {
if !self.containsDelegate(delegate) {
self.delegates.append(delegate)
}
}
func removeDelegate(delegate: ViewControllerSingletonDelegate) {
/* implement any other function by your self :) */
}
func containsDelegate(delegate: ViewControllerSingletonDelegate) -> Bool {
for aDelegate in self.delegates {
if aDelegate === delegate { return true }
}
return false
}
func forwardToDelegate(closure: (delegate: ViewControllerSingletonDelegate) -> Void) {
for aDelegate in self.delegates { closure(delegate: aDelegate) }
}
}
class SomeViewController: UIViewController, ViewControllerSingletonDelegate {
let viewControllerSingleton = ViewControllerSingleton.sharedInstance
func someFunction() { // some function where you'll set the delegate
self.viewControllerSingleton.addDelegate(self)
}
/* I assume you have something like this in your code */
#IBAction func unwindToSomeOtherController(unwindSegue: UIStoryboardSegue) {
self.viewControllerSingleton.forwardToDelegate { (delegate) -> Void in
delegate.viewControllerWillUnwind(unwindSegue.sourceViewController, toViewController: unwindSegue.destinationViewController)
}
/* do something here */
}
// MARK: - ViewControllerSingletonDelegate
func viewControllerWillUnwind(viewcontroller: UIViewController, toViewController: UIViewController) {
/* do something with the callback */
/* set some flag for example inside your view controller so your viewDidAppear will know what to do */
}
}
You also could modify the callback function to return something else, like controller identifier instead the controller itself.
I do everything programmatically, so please don't judge me for that too. ;)
If this code snippet won't help you, I'd still love to see some feedback.
Suppose the segue navigation is ViewController -> FirstViewController -> SecondViewController. There is an unwind from SecondViewController to ViewController. You can add in the intermediary FirstViewController the following code to detect unwind actions.
import UIKit
class FirstViewController: UIViewController {
var unwindAction:Bool = false
override func viewDidAppear(animated: Bool) {
if unwindAction {
println("Unwind action")
unwindAction = false
}
}
override func viewControllerForUnwindSegueAction(action: Selector, fromViewController: UIViewController, withSender sender: AnyObject?) -> UIViewController? {
self.unwindAction = true
return super.viewControllerForUnwindSegueAction(action, fromViewController: fromViewController, withSender: sender)
}
}
EDIT
After giving this some thought, I decided the solution to this depends on the kind of complexity that you are dealing with here. What exactly do you do when you do the unwind segue? The solutions given here are viable and they work -- only if you want to detect whether it is an unwind action. What if you want to pass the data between the point where the unwind is happening to the root? What if there is a complex set of preparations that you wanna do in one of the intermediate view controllers? What if you want to do both of these?
In such complex scenarios, I would immediately rule out overriding the unwind methods of the view controller. Doing such operations there will work, but it won't be clean. A method will be doing what it isn't supposed to do. Smell that? That's code smell.
What if, somehow a view controller could inform the next view controller in the hierarchy of the event happening? Better yet, how do we do this without tightly coupling these two?
Protocol.
Have a protocol definition something like:
protocol UnwindResponding {
prepareForUnwindSegue(segue:UISegue , formViewController:UIViewController, withImportantInfo info:[String,AnyObject])
}
Using protocol you will keep the relationship between the objects -- the hierarchy of view controllers in this case -- explicit. At the point of occurrence of a particular event, you will delegate the call to the next controller in the hierarchy informing about the happening of a particular event in another view controller. Here is an example:
override func prepareForSegue(segue:UIStoryboardSegue, sender:AnyObject?) {
if let unwindResponder = self.presentingViewController as? UnwindResponding where segue.identifier = "unwindSegue" {
unwindResponder.prepareForUnwindSegue(segue:UISegue, fromViewController:self,info:info)
}
}
In the intermediary view controller you can do something like:
extension IntermediaryViewController : UnwindResponding {
prepareForUnwindSegue(segue:UISegue , fromViewController:UIViewController, withImportantInfo info:[String,AnyObject]) {
if let unwindResponder = self.presentingViewController {
unwindResponder.prepareForUnwindSegue(segue,fromViewController:fromViewController, info:info)
}
unwindSegue = true
}
}
Granted, you wouldn't wanna do this if you just want to detect unwind segues. Maybe you do, you'll never know what will happen in the future. Never hurts to keep your code clean.
Add method in your parent view controller
#IBAction func unwindToParent(unwindSegue: UIStoryboardSegue) {
if let childViewController = unwindSegue.sourceViewController as? ChildViewController {
println("unwinding from child")
}
}
As an exemple if the unwind segue is related to a button, in the storyboard link your button to it's view controller exit
It will propose to link to unwindToParent method
Then each time the unwind segue is performed, the unwindToParent method will be called
You can override the function unwindForSegue:towardsViewController:, which is called when the ViewController is on the path of an unwind segue. It's meant to be used to reconfigure the ViewController.
Swift example:
override func unwind(for unwindSegue: UIStoryboardSegue, towardsViewController subsequentVC: UIViewController) {
}

Passing data back from a modal view in WatchKit

When modally presenting, or pushing, an interface controller we can specify the context parameter to pass some data to the new controller as follows.
// Push
[self pushControllerWithName:#"MyController" context:[NSDictionary dictionaryWithObjectsAndKeys:someObject, #"someKey", ..., nil]];
// Modal
[self presentControllerWithName:#"MyController" context:[NSDictionary dictionaryWithObjectsAndKeys:someObject, #"someKey", ..., nil]];
My question is, how can we do the reverse?
Say we present a controller modally for the user to pick an item from a list and we return to the main controller, how can we get the item that has been picked?
I wrote a full example that uses Delegation in WatchKit, passing the delegate instance in the context, and calling delegate function from the modal : Here is the full project example on GitHub
Here is the principale classes of the example :
InterfaceController.swift
This is the main Controller, there are a label and a button on his view. When you press the button, the presentItemChooser get called and it present the ModalView (ModalInterfaceController). I pass the instance of InterfaceController in the context to the modal. Important this controller implements `ModalItemChooserDelegate' functions (the protocol definition is in the modal file)
class InterfaceController: WKInterfaceController, ModalItemChooserDelegate {
#IBOutlet weak var itemSelected: WKInterfaceLabel!
var item = "No Item"
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
// Configure interface objects here.
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
itemSelected.setText(item)
super.willActivate()
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
func didSelectItem(itemSelected: String) {
self.item = itemSelected
}
#IBAction func presentItemChooser() {
self.presentControllerWithName("ModalInterfaceController", context: self)
}
}
ModalInterfaceController.swift
This is the class of my modal controller. I hold the reference of my previous controller (self.delegate = context as? InterfaceController). When a row is selected, I call my delegate function didSelectItem(selectedItem) before dismissing it.
protocol ModalItemChooserDelegate {
func didSelectItem(itemSelected:String)
}
class ModalInterfaceController: WKInterfaceController {
let rowId = "CustomTableRowController"
let items = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5"]
var delegate: InterfaceController?
#IBOutlet weak var customTable: WKInterfaceTable!
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
self.delegate = context as? InterfaceController
// Configure interface objects here.
println(delegate)
loadTableData()
}
override func willActivate() {
// This method is called when watch view controller is about to be visible to user
super.willActivate()
}
override func didDeactivate() {
// This method is called when watch view controller is no longer visible
super.didDeactivate()
}
private func loadTableData(){
customTable.setNumberOfRows(items.count, withRowType: rowId)
for(i, itemName) in enumerate(items){
let row = customTable.rowControllerAtIndex(i) as! TableRowController
row.fillRow(itemName)
}
}
override func table(table: WKInterfaceTable, didSelectRowAtIndex rowIndex: Int) {
let selectedItem = items[rowIndex]
self.delegate?.didSelectItem(selectedItem)
self.dismissController()
}
}
This is how I pass data back to my previous Controller. If is a better way let me know, I'll take it. :)
You can transfer back information via Protocols by passing self within the context:
InterfaceController.m
// don't forget to conform to the protocol!
#interface InterfaceController() <PictureSelectionControllerDelegate>
//...
// in some method
[self pushControllerWithName:#"PictureSelectionController"
context:#{#"delegate" : self}];
And setting the delegate like so:
PictureSelectionController.m
#property (nonatomic, unsafe_unretained) id<PictureSelectionControllerDelegate> delegate;
// ...
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
// Configure interface objects here.
if ([context isKindOfClass:[NSDictionary class]]) {
self.delegate = [context objectForKey:#"delegate"];
}
}
Don't forget to declare your protocol:
PictureSelectionController.h
#protocol PictureSelectionControllerDelegate <NSObject>
- (void)selectedPicture:(UIImage *)picture;
#end
Then you can call that method from PictureSelectionController.m:
- (IBAction)buttonTapped {
// get image
UIImage *someCrazyKatPicture = //...
[self.delegate seletedPicture:someCrazyKatPicture];
}
And receive it in the delegate method within InterfaceController.m:
- (void)selectedPicture:(UIImage *)picture {
NSLog(#"Got me a cat picture! %#", picture);
}
As ghr says, this requires a bit more explanation. The easy (if hacky) way is to make the presenting controller be part of the context that you are passing into the presented controller. That way, you can call back to the presenting controller when you need to. One way to do this is to use an NSDictionary as your context, and store a special key with a reference to the presenting controller. Hope this helps.
I've been testing passing self to the controllers (modal or not) and using didDeactivate as a way to invoke the delegate methods, but the catch is that it's called whenever the screen is dismissed or when a new view is presented. I'm just getting started with WatchKit so I could be totally wrong here.
My delegate
#class Item;
#class ItemController;
#protocol AddItemDelegate <NSObject>
- (void)didAddItem:(ItemController *)controller withItem:(Item *)item;
My root controller
#interface ListController() <AddItemDelegate>
...
- (void)table:(WKInterfaceTable *)table didSelectRowAtIndex:(NSInteger)rowIndex {
// TODO: How do we pass data back? Delegates? Something else?
if ([self.items[rowIndex] isEqualToString:#"Item 1"]) {
// TODO: Do I really want to pass along a single object here?
[self pushControllerWithName:#"Item" context:self];
}
}
...
#pragma mark - AddItemDelegate
- (void)didAddItem:(ItemController *)controller withItem:(Item *)item {
NSLog(#"didAddItem:withItem: delegate called.");
}
My child controller
#property (nonatomic, strong) Item *item;
#property (nonatomic, weak) id<AddItemDelegate> delegate;
...
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
// TODO: Check that this conforms to the protocol first.
self.delegate = context;
}
...
- (void)didDeactivate {
[super didDeactivate];
[self.delegate didAddItem:self withItem:self.item];
}
Passing data back from watchOS interfaceController using block and segue
Passing data back and forth between interfaceControllers is not so simple. There is segue process in WatchKit but the first problem is that there is no prepareForSegue and you couldn't reach segue's destinationViewController, so you couldn't inject stuffs easily to the new controller (WatchOS 3 - 4).
In the backward direction there is no exit so you couldn't reach the unwind segue.
Another problem is that these solutions try to update the data and the user interface of the of the first interfaceController in the willActivate method which is fired any time the watch screen awake - so quite frequently - and this could cause problems and it's complicate.
The programming practice is mainly using delegate and injecting self using the context of the segue, as the above answers describe.
But using delegate is a little bit complicate so I use blocks which is more contemporary and I think better and more elegant.
Let's see how:
First let's prepare the segue in the Interface Builder of the Apple Watch's storyboard, just connect a button with another interfaceController pushing Ctrl button and name the segue.
then in the .h file of the source interfaceController lets's name it SourceInterfaceController.h declare a property for the block:
#property (nonatomic, strong) BOOL (^initNewSessionBlock)(NSDictionary *realTimeDict, NSError *error);
then use contextForSegueWithIdentifier: to transfer the block or any other data to the destination interfaceController using the segueIdentifier if you have more segues.
This Apple method actually use a (id)context as a return object which could be any object and the destination interfaceController's awakeWithContext:(id)context method will use it when the the interfaceController launches.
So let's declare the block in SourceInterfaceController.m then pass it to the context:
- (id)contextForSegueWithIdentifier:(NSString *)segueIdentifier {
__unsafe_unretained typeof(self) weakSelf = self;
if ([segueIdentifier isEqualToString:#"MySegue"]) {
self.initNewSessionBlock = ^BOOL (NSDictionary *mySegueDict, NSError *error)
{
[weakSelf initNewSession];
NSLog(#"message from destination IC: %#", realTimeDict[#"messageBack"]);
return YES;
};
return self.initNewSessionBlock;
}
else if ([segueIdentifier isEqualToString:#"MyOtherSegue"]) {
self.otherBlock = ^BOOL (NSString *myText, NSError *error)
{
//Do what you like
return YES;
};
return self.otherBlock;
}
else {
return nil;
}
}
If you'd like to transfer any more data than just the block with the context to the destination interfaceController, just wrap them in a NSDictionary.
In the destination interfaceController name it DestinationInterfaceController.h let's declare another property to store the block using any name but the same variable declaration
#property (copy) BOOL (^initNewSessionBlock)(NSDictionary *realTimeDict, NSError *error);
then fetch the block from the context in DestinationInterfaceController.m:
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
self.initNewSessionBlock = context;
}
Later in DestinationInterfaceController.m just trigger the block, for example in an action method with a button:
- (IBAction)initNewSessionAction:(id)sender {
NSError *error = nil;
NSDictionary *realTimeDict = #{#"messageBack" : #"Greetings from the destination interfaceController"};
BOOL success = self.initNewSessionBlock(realTimeDict, error);
if (success) {
[self popController];
}
}
The block will be executed any method of the source interfaceController using the data in the scope of the destination interfaceController, so you can send data back to the destination sourceController.
You can pop the interfaceController using popController if everything is ok and the block return yes as a BOOL.
Note: Of course you can use any kind of segue whether it's a push or modal and you can use pushControllerWithName:context: too to
trigger the segue, and you can use this method's context in the same way.
maybe there is some other ways but i prefer to use pushControllerWithName: method.
Root controller:
- (IBAction)GoToChildControllerButton {
[self pushControllerWithName:#"TableInterfaceController" context:#"pass some data to child controller here..."];
}
Child controller:
- (IBAction)BackToRootControllerButton {
[self pushControllerWithName:#"TableInterfaceController" context:#"pass some data back to root controller here..."];
}

iOS 7 UIWebView keyboard issue

I have to remove this bar as here link but for iOS 7 this code does not work.
We remove this bar with some Objective C runtime trickery.
We have a class which has one method:
#interface _SwizzleHelper : NSObject #end
#implementation _SwizzleHelper
-(id)inputAccessoryView
{
return nil;
}
#end
Once we have a web view which we want to remove the bar from, we iterate its scroll view's subviews and take the UIWebDocumentView class. We then dynamically make the superclass of the class we created above to be the subview's class (UIWebDocumentView - but we cannot say that upfront because this is private API), and replace the subview's class to our class.
#import "objc/runtime.h"
-(void)__removeInputAccessoryView
{
UIView* subview;
for (UIView* view in self.scrollView.subviews) {
if([[view.class description] hasPrefix:#"UIWeb"])
subview = view;
}
if(subview == nil) return;
NSString* name = [NSString stringWithFormat:#"%#_SwizzleHelper", subview.class.superclass];
Class newClass = NSClassFromString(name);
if(newClass == nil)
{
newClass = objc_allocateClassPair(subview.class, [name cStringUsingEncoding:NSASCIIStringEncoding], 0);
if(!newClass) return;
Method method = class_getInstanceMethod([_SwizzleHelper class], #selector(inputAccessoryView));
class_addMethod(newClass, #selector(inputAccessoryView), method_getImplementation(method), method_getTypeEncoding(method));
objc_registerClassPair(newClass);
}
object_setClass(subview, newClass);
}
The equivalent of the above in Swift 3.0:
import UIKit
import ObjectiveC
var swizzledClassMapping = [AnyClass]()
extension UIWebView {
func noInputAccessoryView() -> UIView? {
return nil
}
public func removeInputAccessoryView() {
var subview: AnyObject?
for (_, view) in scrollView.subviews.enumerated() {
if NSStringFromClass(type(of: view)).hasPrefix("UIWeb") {
subview = view
}
}
guard subview != nil else {
return
}
//Guard in case this method is called twice on the same webview.
guard !(swizzledClassMapping as NSArray).contains(type(of: subview!)) else {
return;
}
let className = "\type(of: subview!)_SwizzleHelper"
var newClass : AnyClass? = NSClassFromString(className)
if newClass == nil {
newClass = objc_allocateClassPair(type(of: subview!), className, 0)
guard newClass != nil else {
return;
}
let method = class_getInstanceMethod(type(of: self), #selector(UIWebView.noInputAccessoryView))
class_addMethod(newClass!, #selector(getter: UIResponder.inputAccessoryView), method_getImplementation(method), method_getTypeEncoding(method))
objc_registerClassPair(newClass!)
swizzledClassMapping += [newClass!]
}
object_setClass(subview!, newClass!)
}
}
I've made a cocoapod based on this blog post from #bjhomer.
You can replace the inputaccessoryview and not just hide it. I hope this will help people with the same issue.
https://github.com/lauracpierre/FA_InputAccessoryViewWebView
You can find the cocoapod page right here.
I've came across this awesome solution, but I needed to get the inputAccessoryView back as well. I added this method:
- (void)__addInputAccessoryView {
UIView* subview;
for (UIView* view in self.scrollView.subviews) {
if([[view.class description] hasSuffix:#"SwizzleHelper"])
subview = view;
}
if(subview == nil) return;
Class newClass = subview.superclass;
object_setClass(subview, newClass);
}
It does seem to work as intended with no side effects, but I can't get rid of the feeling that my pants are on fire.
If you want Leo Natan's solution to work with WKWebView instead of UIWebView just change prefix from "UIWeb" to "WKContent".
I created a gist to accomplish this:
https://gist.github.com/kgaidis/5f9a8c7063b687cc3946fad6379c1a66
It's a UIWebView category where all you do is change the customInputAccessoryView property:
#interface UIWebView (CustomInputAccessoryView)
#property (strong, nonatomic) UIView *customInputAccessoryView;
#end
You can either set it to nil to remove it or you can set a new view on it to change it.
Keep in mind, this also uses private API's, so use at your own risk, but it seems like a lot of apps do similar things nonetheless.

xcode storyboard Container View - How do I access the viewcontroller

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.

How to tell if UIViewController's view is visible

I have a tab bar application, with many views. Is there a way to know if a particular UIViewController is currently visible from within the UIViewController? (looking for a property)
The view's window property is non-nil if a view is currently visible, so check the main view in the view controller:
Invoking the view method causes the view to load (if it is not loaded) which is unnecessary and may be undesirable. It would be better to check first to see if it is already loaded. I've added the call to isViewLoaded to avoid this problem.
if (viewController.isViewLoaded && viewController.view.window) {
// viewController is visible
}
Since iOS9 it has became easier:
if viewController.viewIfLoaded?.window != nil {
// viewController is visible
}
Or if you have a UINavigationController managing the view controllers, you could check its visibleViewController property instead.
Here's #progrmr's solution as a UIViewController category:
// UIViewController+Additions.h
#interface UIViewController (Additions)
- (BOOL)isVisible;
#end
// UIViewController+Additions.m
#import "UIViewController+Additions.h"
#implementation UIViewController (Additions)
- (BOOL)isVisible {
return [self isViewLoaded] && self.view.window;
}
#end
There are a couple of issues with the above solutions. If you are using, for example, a UISplitViewController, the master view will always return true for
if(viewController.isViewLoaded && viewController.view.window) {
//Always true for master view in split view controller
}
Instead, take this simple approach which seems to work well in most, if not all cases:
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
//We are now invisible
self.visible = false;
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
//We are now visible
self.visible = true;
}
For those of you looking for a Swift 2.2 version of the answer:
if self.isViewLoaded() && (self.view.window != nil) {
// viewController is visible
}
and Swift 3:
if self.isViewLoaded && (self.view.window != nil) {
// viewController is visible
}
For over-full-screen or over-context modal presentation, "is visible" could mean it is on top of the view controller stack or just visible but covered by another view controller.
To check if the view controller "is the top view controller" is quite different from "is visible", you should check the view controller's navigation controller's view controller stack.
I wrote a piece of code to solve this problem:
extension UIViewController {
public var isVisible: Bool {
if isViewLoaded {
return view.window != nil
}
return false
}
public var isTopViewController: Bool {
if self.navigationController != nil {
return self.navigationController?.visibleViewController === self
} else if self.tabBarController != nil {
return self.tabBarController?.selectedViewController == self && self.presentedViewController == nil
} else {
return self.presentedViewController == nil && self.isVisible
}
}
}
You want to use the UITabBarController's selectedViewController property. All view controllers attached to a tab bar controller have a tabBarController property set, so you can, from within any of the view controllers' code:
if([[[self tabBarController] selectedViewController] isEqual:self]){
//we're in the active controller
}else{
//we are not
}
I made a swift extension based on #progrmr's answer.
It allows you to easily check if a UIViewController is on screen like so:
if someViewController.isOnScreen {
// Do stuff here
}
The extension:
//
// UIViewControllerExtension.swift
//
import UIKit
extension UIViewController{
var isOnScreen: Bool{
return self.isViewLoaded() && view.window != nil
}
}
For my purposes, in the context of a container view controller, I've found that
- (BOOL)isVisible {
return (self.isViewLoaded && self.view.window && self.parentViewController != nil);
}
works well.
I use this small extension in Swift 5, which keeps it simple and easy to check for any object that is member of UIView.
extension UIView {
var isVisible: Bool {
guard let _ = self.window else {
return false
}
return true
}
}
Then, I just use it as a simple if statement check...
if myView.isVisible {
// do something
}
I hope it helps! :)
Good point that view is appeared if it's already in window hierarchy stack.
thus we can extend our classes for this functionality.
extension UIViewController {
var isViewAppeared: Bool { viewIfLoaded?.isAppeared == true }
}
extension UIView {
var isAppeared: Bool { window != nil }
}
XCode 6.4, for iOS 8.4, ARC enabled
Obviously lots of ways of doing it. The one that has worked for me is the following...
#property(nonatomic, readonly, getter=isKeyWindow) BOOL keyWindow
This can be used in any view controller in the following way,
[self.view.window isKeyWindow]
If you call this property in -(void)viewDidLoad you get 0, then if you call this after -(void)viewDidAppear:(BOOL)animated you get 1.
Hope this helps someone. Thanks! Cheers.
if you're utilizing a UINavigationController and also want to handle modal views, the following is what i use:
#import <objc/runtime.h>
UIViewController* topMostController = self.navigationController.visibleViewController;
if([[NSString stringWithFormat:#"%s", class_getName([topMostController class])] isEqualToString:#"NAME_OF_CONTROLLER_YOURE_CHECKING_IN"]) {
//is topmost visible view controller
}
The approach that I used for a modal presented view controller was to check the class of the presented controller. If the presented view controller was ViewController2 then I would execute some code.
UIViewController *vc = [self presentedViewController];
if ([vc isKindOfClass:[ViewController2 class]]) {
NSLog(#"this is VC2");
}
I found those function in UIViewController.h.
/*
These four methods can be used in a view controller's appearance callbacks to determine if it is being
presented, dismissed, or added or removed as a child view controller. For example, a view controller can
check if it is disappearing because it was dismissed or popped by asking itself in its viewWillDisappear:
method by checking the expression ([self isBeingDismissed] || [self isMovingFromParentViewController]).
*/
- (BOOL)isBeingPresented NS_AVAILABLE_IOS(5_0);
- (BOOL)isBeingDismissed NS_AVAILABLE_IOS(5_0);
- (BOOL)isMovingToParentViewController NS_AVAILABLE_IOS(5_0);
- (BOOL)isMovingFromParentViewController NS_AVAILABLE_IOS(5_0);
Maybe the above functions can detect the ViewController is appeared or not.
If you are using a navigation controller and just want to know if you are in the active and topmost controller, then use:
if navigationController?.topViewController == self {
// Do something
}
This answer is based on #mattdipasquale's comment.
If you have a more complicated scenario, see the other answers above.
you can check it by window property
if(viewController.view.window){
// view visible
}else{
// no visible
}
I needed this to check if the view controller is the current viewed controller, I did it via checking if there's any presented view controller or pushed through the navigator, I'm posting it in case anyone needed such a solution:
if presentedViewController != nil || navigationController?.topViewController != self {
//Viewcontroller isn't viewed
}else{
// Now your viewcontroller is being viewed
}
Window:
window.isVisible
viewController.view.window?.isVisible ?? false
View (macOS):
extension NSViewController {
var isOnScreen: Bool {
return ( self.isViewLoaded && view.window != nil )
}
}

Resources