From within a UIViewController, I programmatically create about 10 instances of a CustomUIView and place them on the [UIViewController view].
When any of those instances of CustomUIView is touched, I want to let the controller know and take some action.
I've thought of two ways to do this:
Subscribe the UIViewController as an observer to the CustomUIView. Then when touchesBegan:withEvent: is fired, call a method on the UIViewController observer.
Use [self.nextResponder touchesBegan:touches withEvent:event] to raise the event to the UIViewController.
I've implemented both, and they both work ok.
Question: What is the best way for a programmatically-created view to communicate with its UIViewController? Is one of these the way to go? Or is there a third way?
The resources I've found online are very good at explaining all the Cocoa "pieces", but I'm having trouble finding best practices for overall architecture.
For the case of touch events, you don't have to do anything!
From the UIViewController class reference:
View controllers are tightly bound to the views they manage and take part in the responder chain used to handle events. View controllers are themselves descendants of the UIResponder class and are inserted into the responder chain between the managed root view and its superview, which typically belongs to a different view controller. If the view controller’s view does not handle an event, the view controller itself has the option of handling the event before passing the event along to the superview.
This means your view controller can handle touch events in its view without any extra setup, as long as you don't handle them in your view.
You would make the UIViewController the delegate for the UIViews, if you want the views to be able to communicate back to the controller.
Touch events will automatically get sent to the Controller as part of the responder chain.
Related
I have the following structure in my app:
Custom View Controller
+- Custom View 1
+- Custom View 2
+- A number of UIControls
If the user taps one of the UIControls I would like to send a message to my custom view controller.
Currently I can see two solutions for this:
Tell the 1st custom view about the controller, then tell the 2nd custom view about it as well, and set the target and action when I create the 'UIControl's. (My custom views could have a -initWithFrame:controller: method or something)
The UIControl could send an NSNotification (possibly with some userInfo) that my controller observes.
I'm leaning toward option 2 because I dislike telling Custom View 1 about my controller, just so it can tell Custom View 2 about it.
What are the pros and cons for my two solutions, or is there another way to do this?
Update: I went with the NSNotification for now.
How about you keep a pointer to your Custom View Controller from your app delegate and expose it as a property.
Then you can use the static sharedApplication message on UIApplication to get to your app delegate and the corresponding property:
// in custom view 2 code ...
YourApplication * app = (YourApplication*)[UIApplication sharedApplication];
CustomViewController * cvc = app.customViewController;
There are several possibilities but the best one really depends on your particular business case, and the purposes of several nested UIView subclasses.
Assign a value to the tag property of the control, then access it using viewWithTag: from the view controller when you are setting up it's target-action events.
Provide public accessors on each UIView subclass for each subview, then access the control from the view controller using these properties.
Use the responder chain. Specify a nil target when setting up the control's target-action and the action will eventually be sent to the view controller if it implements it.
Depending on the nature of your UIView subclasses, it might be better to implement any necessary callbacks to the view controller through a delegate protocol of the view. So the target-action of the control would be assigned by the containing UIView subclass to itself, and in it's handler it would call it's own delegate method.
I have created an UIView in my iPhone app. I want to handle something when user closes or opens when UIView is present as current screen. I thought, i can do this under viewWillAppear:. But, viewWillAppear: is not called in UIView. Does it work only on UIViewController? How can i handle viewWillAppear: or viewDidAppear: for an UIView?
Update: UIView what I created everything through program, not in .xib.
Please advise.
Thanks!
From your message I infer that you wrote your viewWillAppear: method on the UIView class. As you suspect, that method is part of [UIViewController]1, not [UIView]2 therefore it only gets called on the UIViewController.
You should connect the property view of the UIViewController to the UIView object in the interface builder and then implement that method in the UIViewController.
If your view is created in response to an user action,
Update for your update:
You should tag the views either in code (view.tag=1) or IB.
Then you can do if (self.window.rootViewController.view.tag == 1) { ... } from your delegate (assuming you are looking for the view of the controller who is the rootController, otherwise post more details).
It's even better if you define constants on one place instead writing 1 as a literal.
These delegate methods are called every time the superview is presented to the screen and should be implemented in the UIViewControllers.
The gotcha is that these methods aren't called when subviews are presented on the screen, so your superview-view-controller will have to respond to these events accordingly.
You can find more information in this post here.
If you study the documentation for UIView and UIViewController what you will find is -(void)viewWillAppear:animated: is a method of UIViewController and not of UIView, so in order to use it, it must be implemented by subclassing UIViewController. Generally for best practice if you want to follow MVC, any functionality that does not pertain to the view itself should be delegated to the view controller and not be in the body of your UIView subclass.
Create a new view controller with xib file, and then link your custom view class to the view in your xib file.
When/why/how would you use these methods?
- navigationController:willShowViewController:animated:
– navigationController:didShowViewController:animated:
Can't you just use these UIViewController Instance Methods instead?
– viewWillAppear:
– viewDidAppear:
– viewWillDisappear:
– viewDidDisappear:
You'd use the first ones if you want to be informed about these events outside the visible view controllers. The delegates allow you to get a notification at a single point. Using UIViewController's methods bind you within these controllers, where you'd have to write/call same code multiple times to achieve the same.
Generally you'd divide tasks into these two groups:
Things that happen across all view controllers: use the delegates
Things happening within a single view controller: use the instance methods
The UINavigationControllerDelegate protocol defines methods a navigation controller delegate can implement to change the behavior when view controllers are pushed and popped from the stack of a navigation controller.
These method are important when you need to perform some actions that would not be on the scope of your view controller. the delegate it's supposed to be a object predecessor of your view controller on the hierarchy and that would be interested in perform some actions without the knowing of each view controller that is pushed or popped, these actions are not necessarily related with that view controller specifically, but they can be methods called on other objects.
Is there ever a time when the touchesBegan, touchesMoved, and touchesEnded methods would be handled inside a UIViewController vs. UIView?
I understand that they are typically used in a custom UIView but I am curious about this situation.
Thanks
I would say the most likely time that you would want to put this stuff into the controller is when you are not subclassing UIView. I tend to put this in the controller to avoid subclassing just to add the touches methods.
Also, depending on what you are doing in the methods, it may be better MVC to put it in the controller. I guess you should consider if your code is "View" or "Controller" code and place it appropriately.
Yes, they can be used in a view controller, and I have used this method multiple times. All you have to do is define these methods in the view controller and they will be called as it moves up the responder chain.
Just as a matter of curiosity, how does the nextResponder method implementation of the UIView class know who is the UIViewController that manages the view?
The UIResponder documentation states this, and I can see it work, but I don't quite understand it. To the best of my knowledge, a UIView does not maintain a reference to it's controller, so what's happening behind the scenes? Or am I just missing something obvious?
I'm still very new to Objective-C and iPhone development so I apologize if this is something obvious, but I am quite curious.
Thanks!
The responder chain is separate from the view hierarchy. The responder chain might look like this:
First Responder > View Hierarchy > Window > Window Delegate > etc...
However, objects can insert themselves into the responder chain and that's what UIViewController does. From the docs:
Because view controllers are tightly bound to the views they manage, they are also part of the responder chain used to handle events. View controllers are themselves descendants of the UIResponder class and are inserted into the responder chain between the managed view and its superview.
In Big Cocoa, this is accomplished using the -setNextResponder: method. That method isn't public in Cocoa Touch, but nevertheless that's what UIViewController appears to do.
If you look at UIView.h, you can see a member variable named _viewDelegate that is of type UIViewController*, this is probably where the view controller reference is stored when the viewcontroller's view property is set, and where it knows to look when you call nextResponder.
there is actually a private api on uiview to get the uiviewcontroller it belongs to. Very handy ;)
My guess is when a view is added to a view controller(controller.view) this property is set.
My guess is that the system may maintain a mapping between UIViewController objects and their root UIView objects. The code for traversal of responder chain may use this mapping to pass the event to the corresponding UIViewController object.
Typically subviews are added using:
subview = [viewController view]
[superview addSubview subview]
addSubview method automatically sets superview as the next responder for subview, so:
a) viewController wont have the opportunity to insert itself between superview and subview.
b) viewController does not know about superview, hence it cannot set it as its next responder.
c) Apple recommends that a view may not be shared across controllers. In absence of multi-threading this restriction makes sense only if there is a map of views and view controllers.