Best way to send a message to a View Controller from a deeply nested UIControl? - iphone

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.

Related

separate view from controller

In order to respect the MVC pattern, I would like to dissociate the view from the controller.
For example
QuestionView (inherits from UIVIew)
QuestionViewController (inherits from UIViewController
In my controller, I set the view self.view = ...
But when I push a button in the view, it should call a method from the controller BUT the view shouldn't know its controller right ?
So how can I link the view to the controller ?
Set button target as QuestionViewController's object programmatically.
With iOS in most cases you can get the job done with only sub classing the controller part, and not the views. So you use the UIKit provided classes straight 'out of the box'.
It's is possible because:
The layout: this can be stored in a Nib file and be loaded by the controller.
responding to user events: the UIcontrols have generic callback mechanisms: delegates and actions. The 'connection' is either made in the nib file, or in the controller code.
Personally, I only subclass views when I need custom drawing.
So, the View INSTANCE obviously 'knows' its controller, but it is all done through generic interfaces, and so the view CODE is ignorant of your controller.

How to create custom view controller container using storyboard in iOS 5

In iOS5 using storyboard feature I want to create a custom container which will have 2 ViewControllers embedded in it. For Example, embed Table view controller as well as a view controller both in one ViewController.
That is, one view controller will have 2 relationship:
to table view controller
to view controller which in turn will have 4 UIImage view Or UIButton in it
Is creating this type of relationship possible using storyboard's drag drop feature only & not programmatically?
,You should only have one view controller to control the scene. However, this viewController might have two other view controllers that control particular subviews on your scene. To do this you create properties in your scene viewController, in your case one for your tableViewController and one for your view. I like to keep things together so I make both these viewControllers outlets and create them in interface builder. To create them in interface builder pull in an Object from the Object library and set its type to the relevant viewController. Hook it up to the appropriate outlet you just created in your scene's viewController - Note: this is important otherwise the viewController will be released if you are using ARC and crash your app. Then hook these viewControllers up to the view you want them to control and you are done.
Alternatively you can instantiate and hop up your viewControllers in your scenes viewController should you prefer to do this.
Hope this helps.
Edit: On reflection this is not a good idea and actually goes against the HIG you should maintain only one ViewController for each screen of content and instead try to create a suitable view class and have the single view controller deal with the interactions between the various views.
There is a way to do it that isn't too hacky. It is described at the following URL for UITabBarControllers, which you could use the first view controller in the list control the first subview, and the second one control the other. Or, you can probably adapt the code to work with UISplitViewController.
http://bartlettpublishing.com/site/bartpub/blog/3/entry/351
Basically, it works by replacing the tabbarcontroller at runtime after iOS has finished configuring it.

Programmatically creating a UIButton inside a custom UIView subclass, how does one access the file owner to add a target method? delegates?

I have a root view controller which loads a custom UIView subclass I have created and adds it as a subview.
Inside this custom UIView subclass I code/generate a UIButton in the awakeFromNib method.
Is there a simple way to access the File Owner without creating a delegate if the UIButton's action method is inside the root view controller?
E.g
[myButton addTarget:[self.file_owner ?] action:#selector(methodInFileOwner:) ....
Using Interface Builder it's still easy to assign a UIView my custom UIView subclass and just drag a UIButton's selector reference to the file owner. Voila!
How is this done through code though? Do I have to create a delegate and use
[myButton addTarget:[self.delegate] ...
?
File's Owner is an Interface Builder concept. It doesn't exist on the programming side, basically, because it's not needed. In interface Builder, File's Owner is the class that instantiates the nib file. Often, it just refers to the class of the nib file you're currently working with. Since you're working with a view controller, the File's Owner is your view controller subclass, and it allows you to make connections to instance variables and methods of that class.
On the programming side, in this case, the equivalent of File's Owner would just be self. And, you access an instance variable, using properties, as self.instanceVariable.
On to your question. If you want the selector method to be in the view controller, that makes perfect sense. But then, the view controller can create the button, set its target/action, and add it as a subview to the custom view. You could do this in -viewDidLoad, which is called after the nib file is loaded and is the standard place where you would make any programmatic additions to the view controller. So, you could do it as follows:
- (void)viewDidLoad {
self.myButton = [[[UIButton alloc] initWithFrame:CGRectMake(x, y, width, height)] autorelease];
self.myButton.buttonType = ...;
[myButton addTarget:self action:#selector(actionMethod)...];
self.myCustomView = [[[MyCustomViewClass alloc] initWithFrame:...] autorelease];
[self.myCustomView addSubview:self.myButton.view];
[super viewDidLoad];
}
The above code is just an example. You can initialize your objects in different ways. In this case, the button would now be an instance variable of the view controller. But, you could just as easily leave it in the custom view and just refer to it as: self.myCustomView.myButton
I hope this is helpful.
Correction: The above code should be in viewWillAppear rather than viewDidLoad. When viewDidLoad is called, the geometry (i.e. the view's bounds) has not yet been set. So, in order to set the frame of any object, it must be done in viewWillAppear.
The target should be an object of the root view controller class. In your UIView subclass you will need a reference to your root view controller class.
If a nested widget is hidden from the controller, then that essentially means that the custom view should manage all aspects of that nested widget. Here are some options (and probably not a complete list of them either):
You could have the custom subview handle UIControl events and propagate them into the button. Your custom subview would implement the methods of UIControl and essentially hand them down to the button.
You could also use a delegate like you mentioned.
Or you could restructure it so that the widget hierarchy is flattened, but their display is nested.
If you plan on making a custom component that you reuse in multiple places, then the first and second options are probably better since they are more flexible. If this is not the case, the third option is probably best since there is actual interaction between the button and the controller.
The Delegates and DataSources section of the Cocoa Fundamentals Guide gives an example of what the code looks like to create delegates.

iPhone: viewWillAppear is not called for UIView

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.

Actionscript style events in Objective-C?

I'm a newbie doing Objective-C, coming from Flex/Actionscript development.
I have an iPhone app with an UIApplicationDelegate conforming delegate called MyAppDelegate - it has a UIWindow.
Instead of adding buttons, labels and whatnot directly to the window, I guess I'm supposed to make a child class of UIViewController for every screen I wanna make. When that screen should be displayed, I add the respective controller's view to the window as a subview. When another screen should be displayed, I should pop off any other view from the window and add the new view. Hope I got everything correct so far...
My intent is to make each view controller only know about its own things, so let's say I wanna call view A from view B, in ActionScript I'd add a button in A firing off an event which would be caught in view A's owning object (could be the application), which could take proper action (remove view A, instantiate view B and display it).
How do I do this in Objective-C?
A UIControl, such as UIButton, can have any number of event listeners registered with:
- (void)addTarget:(id)target action:(SEL)action forControlEvents:(UIControlEvents)controlEvents;
The target would be the view controller you want to receive the method, and the action is the method you want called. For a button, events is usually just UIControlEventTouchUpInside. If the target is nil, the event will pass up the responder chain until a responder implements the action. If you pass #selector(buttonClicked:) then the target should have this method:
-(IBAction) buttonClicked:(id)sender;
The sender will be the button that was clicked. IBAction is equivalent to a void return type. You can bind the action in Interface Builder if you prefer that to doing it programmatically.
When another screen should be
displayed, I should pop off any other
view from the window and add the new
view.
This is basically correct, but usually you use a meta view controller like UINavigationController to manage view controllers. Even if you do not use the UI that a meta controller might present, it is convenient to have view switching managed for you.
If you're coming from Actionscript you may be interested in looking at the PureMVC framwork for objective C. Using PureMVC you'll create a combination of Mediators, Commands, and Models for application interaction.
With PureMVC you register notification with the facade, and you define listeners in your mediators to respond to these. This is pretty close to the event model you're used to in Actionscript. (At my last job we added some categories to the UIResponder to remove some of the cruft in doing this). If you're creating a considerably sized application, then I would recommend you give it a look; it certainly helped us keep everything manageable.
If you don't want to pull in a third party library then you should define your view manipulation code in your MyAppDelegate and use the [UIApplication sharedApplication] class method to access the globally shared instance.