I have a UIViewController which creates a custom sub view. The sub view is a UIView object which has been subclassed a few times.
Within the subview class I create a custom init method:
-(id)init {
self = [super init];
if (self) {
// Init code
[self spm_correctGuessViewCustomInit];
}
return self;
}
And within this I create a button and a label. The question relates directly to the button and its target action.
What I would like is for the UIViewController to have the buttons action, not the subclasses UIView (which actually creates and holds the button).
[continueButton addTarget:self.superview action:#selector(correctGuessContinueButtonPressed) forControlEvents:UIControlEventTouchUpInside];
I pass in the target of self.superview, this appears to work correctly and the correct method is run. However, I am shown a warning in the subclass 'Undeclared selector 'correctGuessContinueButtonPressed''
So am I implementing this approach correctly? Please let me know if more information is required.
One solution would be to update your custom view's init method so it takes target and action parameters (much like the addTarget: method of the button). You could then pass these values to the button via the addTarget: call.
- (instancetype)initWithTarget:(id)target action:(SEL)action {
// your normal init code here
// use target and action to setup your button
}
Your view.superview approach will not bring you to the view controller, but to a view.
You can import the header of your implementing class to fix the warning, but I think your design should be improved. Views should work pretty much on their own and not depend on their superviews, or even worse the whole architecture of views and controllers.
I'd pass a delegate down the line that gets called when the user pressed the button, or set some blocks on the views that get called when buttons fire.
Avoid communication over several layers of abstraction.
Reference previous similar question: Calling a method in a UIViewController from a UIButton in a subview
I had to add an import to the View Controller that the method was on, within the custom UIView subclass.
With the controller property set I could set the button target as controller
Ensure that the method that was being called from the button was in the controller header file, so could be seen by the subview implementation file. Previously this was not so the subview was not to know this existed.
Related
I am new to iOS development and I am currently reading the book : iOS Programming (Objective C) by Big Nerd Ranch.
I am confused as in where to initialize subviews such as UIButtons, UIImageView while creating views programtically:
Should the intialization be done in the Main UIView i.e in the
initWithFrame method and maintain a additional weak reference to the subview in the UIView.
or
should I do it in the UIViewControllers loadView method and maintain a weak reference to the subview in the uiviewcontroller (Same approach used while creating UIVew using the interface builder).
I have seen both the approaches being used in various stackoverflow posts but no post that explains which approach is the right one.
you can initialize as per your app's requirement. If any view or button or anything is part of initial setup of your app then you should initialize it in viewDidload.
Now, for example there is requirement like user press button and then new view will be created then you can initialize view in button's click method etc.
So, it's depends on your requirement.
Static views which will live from start to and of app should be initialize in viewdidload, because this is the first method getting called of viewcontroller.
hope this will help :)
It dependes on which architecture you are using. Apple raises the flag of Model-View-Controller, but in fact, UIViewControllers are the View.
For Example:
Let's say that you have a pretty LoginViewController. When you instantiate it, you will be doing something like
LoginViewController *loginVC = [[LoginViewController alloc] init];
At this point, no view is loaded. Your ViewController has just executed the init method, nothing else. When the system calls
loginVC.view
the first method to be executed will be
- (void)loadView;
there you should do exactly that, load your view. So, the approach i like is to have an additional LoginView.
- (void)loadView
{
// you should have a property #property (nonatomic, strong) LoginView *loginView;
self.loginView = [[LoginView alloc] init];
self.view = self.loginView;
}
and in the LoginView init method, you should put your code to build up the view.
However, you could eliminate LoginView, and instantiate all your subviews like this:
- (void)loadView
{
self.view = [[UIView alloc] init];
UIButton *button = [[UIButton alloc] initWithTargetBlaBlaBla...];
[self.view addSubview:button];
// add more fancy subviews
}
In my experience, the first approach is much cleaner than the second one. It also makes version control a lot easier (try to merge a xib, I dare you). I always use MyView.m to build the view (a.k.a setup constriants, style) and use MyViewController.m things like animations, lifeCycle. I like to think that MyView.m is the programatic xib, so anything that you can do with xibs, you should me able to do it inside your view.
Hope it helps!!
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 9 years ago.
Improve this question
I have a custom UIView which has been loaded into a different UIViewController. I have a button in the custom UIView, which calls a method inside its own class. I want this method to refresh the UIViewController which the UIView is embeded in. Currently I have tried importing the UIViewController to the UIView initializing it and then calling a public method which i have put inside the UIViewController. Nothing that happens inside this method seems to affect it though, I have even tried changing the navigation bar title and that wont work. I know the method is getting called though as it comes up in my log.
Any help?
This is where a delegation pattern comes into action. If I have not misunderstood, you want to perform some action(refresh??) on a UIViewController based on some action in a UIView, which is in turn is a part of its own view hierarchy.
Lets say your custom view is wrapped in a class CustomView. It has a method named action that is invoked at some point. Moreover, lets assume that you have used CustomView instances to some view controllers, namely, MyViewController1, MyViewController2, etc, as a part of their view hierarchies. Now, you want to perform some action (refresh) in your VCS when action method is triggered from your CustomView instances. For this purpose, you need to declare a protocol in the header file of your CustomView and will have to register a handler of this protocol (commonly known as delegate) to an instance of the CustomView. The header file would look something like this:
//CustomView.h
//your include headers...
#class CustomView;
#protocol CustomViewDelegate <NSObject>
-(void)customViewDidPerformAction:(CustomView*)customView;
#end
#interface CustomView : UIView {
//......... your ivars
id<CustomViewDelegate> _delegate;
}
//.......your properties
#property(nonatomic,weak) id<CustomViewDelegate> delegate;
-(void)action;
//....other declarations
#end
Now, you would like customViewDidPerformAction method to be called from any class (like a view controller for "refresh" purpose) whenever action method is triggered. For that purpose, in your implementation file (CustomView.m), you need to invoke the customViewDidPerformAction method stub, if available, from inside your action method:
//CustomView.m
//.......initializer and other codes
-(void)action {
//other codes
//raise a callback notifying action has been performed
if (self.delegate && [self.delegate respondsToSelector:#selector(customViewDidPerformAction:)]) {
[self.delegate customViewDidPerformAction:self];
}
}
Now, any class that conforms to CustomViewDelegate protocol can register itself as a receiver of the callback customViewDidPerformAction:. For example, lets say, our MyViewController1 view controller class conforms to the protocol. So the header to the class would look something like this:
//MyViewController1.h
//include headers
#interface MyViewController1 : UIViewController<CustomViewDelegate>
//........your properties, methods, etc
#property(nonatomic,strong) CustomView* myCustomView;
//......
#end
Then you need to register this class as a delegate of myCustomView, after instantiating myCustomView. Say you have instantiated myCustomView in the viewDidLoad method of the VC. So the method body would be something similar to:
-(void)viewDidLoad {
////...........other codes
CustomView* cv = [[CustomView alloc] initWithFrame:<# your frame size #>];
cv.delegate = self;
self.myCustomView = cv;
[cv release];
////......other codes
}
Then you also need to create a method with the same method signature as the protocol declares, inside the implementation(.m) file of the same VC and right your "refresh" code there:
-(void)customViewDidPerformAction:(CustomView*)customView {
///write your refresh code here
}
And you are all set. MyViewController1 would execute your refresh code whenever action method is performed by CustomView.
You can follow the same mechanism (conform to CustomViewDelegate protocol and implement customViewDidPerformAction: method) from within any VC, containing a CustomView in its view hierarchy, to refresh itself whenever the action is triggered.
Hope it helps.
I've subclassed UITextField in order to show a tool bar with a button when the keyboard is shown.
UITextField SubClass:
- (void)methodName
{
//Do stuff
}
When the user presses the button and methodName is triggered inside the UITextField.
I want the same method to be triggered also inside the current ViewController for some more specific code additions relevant only to the current ViewController
I thought about getting a referral to the topviewcontroller and trigger my method from inside methodName in the UITextField subClass but it doesn't feel right.
What's the right way to do the above?
I would look into the delegation pattern. It may make sense to have your view controller be the delegate of your text field so it is informed when your text field is pressed.
Something like:
textField.delegate = viewController;
And have your view controller class implement the appropriate UITextFieldDelegate methods so that it is notified when editing begins/ends.
Suppose you implement a custom table view and a custom view controller (which mostly mimics UITableViewControllers behaviour, but when initialized programmatically, ...
#interface Foo : MyCustomTableViewController ...
Foo *foo = [[Foo alloc] init];
... foo.view is kind of class MyCustomTableView instead of UITableView:
// MyCustomTableView.h
#protocol MyTableViewDelegate <NSObject, UITableViewDelegate>
// ...
#end
#protocol MyTableViewDataSource <NSObject, UITableViewDataSource>
// ...
#end
#interface MyCustomTableView : UITableView
// ...
#end
// MyCustomTableViewController.h
#interface MyCustomTableViewController : UIViewController
// ...
#end
How should you implement/override init methods in correct order/ways so that you could create and use an instance of MyCustomTableView both by subclassing MyCustomTableViewController programmatically or from any custom nib file by setting custom class type to MyCustomTableView in Interface Builder?
It important to note that this is exactly how UITableView (mostly UIKit for that matter) works right now: a developer could create and use either programmatically or by creating from nib, whether be it File owner's main view or some subview in a more complex hierarchy, just assign data source or delegate and you're good to go...
So far I managed to get this working if you subclass MyCustomTableViewController, where I will create an instance of MyCustomTableView and assign it to self.view in loadView method; but couldn't figure out how initWithNibName:bundle:, initWithCoder:, awakeFromNib, awakeAfterUsingCoder:, or whatever else operates. I am lost in life cycle chain and end up with a black view/screen each time.
Thanks.
It is a real mystery how the UITableViewController loads its table regardless of if one is hooked up in interface builder, however I have came up with a pretty good way to simulate that behavior.
I wanted to achieve this with a reusable view controller that contains a MKMapView, and I figured out a trick to make it happen by checking the background color of the view.
The reason this was hard is because any call to self.view caused the storyboard one to load or load a default UIView if didnt exist. There was no way to figure out if inbetween those 2 steps if the user really didn't set a view. So the trick is the one that comes from a storyboard has a color, the default one is nil color.
So now I have a mapViewController that can be used in code or in storyboard and doesn't even care if a map was set or not. Pretty cool.
- (void)viewDidLoad
{
[super viewDidLoad];
//magic to work without a view set in the storboard or in code.
//check if a view has been set in the storyboard, like what UITableViewController does.
//check if don't have a map view
if(![self.view isKindOfClass:[MKMapView class]]){
//check if the default view was loaded. Default view always has no background color.
if([self.view isKindOfClass:[UIView class]] && !self.view.backgroundColor){
//switch it for a map view
self.view = [[MKMapView alloc] initWithFrame:CGRectZero];
self.mapView.delegate = self;
}else{
[NSException raise:#"MapViewController didn't find a map view" format:#"Found a %#", self.view.class];
}
}
The strategy I've used when writing such classes has been to postpone my custom initialization code as late as possible. If I can wait for viewDidLoad or viewWillAppear to do any setup, and not write any custom code in init, initWithNibName:bundle: or similar methods I'll know that my object is initialized just like the parent class no mater what way it was instantiated. Frequently I manage to write my classes without any overrides of these init methods.
If I find that I need to put my initialization code in the init methods my strategy is to write just one version of my initialization code, put that in a separate method, and then override all the init methods. The overridden methods call the superclass version of themselves, check for success, then call my internal initialization method.
If these strategies fail, such that it really makes a difference what way an object of this class is instantiated, I'll write custom methods for each of the various init methods.
This is how I solved my own issue:
- (void)loadView
{
if (self.nibName) {
// although docs states "Your custom implementation of this method should not call super.", I am doing it instead of loading from nib manually, because I am too lazy ;-)
[super loadView];
}
else {
self.view = // ... whatever UIView you'd like to create
}
}
I just witnessed a very strange issue where my view would ignore all of the delegate calls coming from a custom view because I called alloc/init on the item at the load. I'm curious as to why.
#synthesize customTextField;
-(void)viewDidLoad {
// by calling this alloc/init, none of the changes here actually update to the view
// everything is ignored from here on in.
// if I comment out the alloc/init line, everything works fine
self.customTextField = [[UITextField alloc] init];
self.customTextField.text = #"Some text";
// setting font and size as well
}
While I would still get calls to the text field delegate methods, none were linked to my specific text fields. I could not respond to just customTextField.
I do realize that calling alloc/init will give me a completely new instance of customTextField... but why wouldn't that new instance be linked to IB and my view?
Because IB linking != binding.
When you link a variable in IB, it's a simply sets the variable once on first load, that's it. It does no other special code to track any changes to it, for good reason.
For example:
You are designing a UITableViewCell, and if you have a cell that is selected, you must rearrange all of the content inside the cell. In this case, you determined it would be easier if you just recreated all of the subviews and re-added them into the view, so you do the following:
-(void) layoutSubviews {
if (cellIsSelected)
{
// custom button is an IBOutlet property, which is by default a subview of self
self.customButton = [UIButton buttonWithType:UIButtonTypeCustom];
[[self someSubView] addSubview:customButton];
}
else {
// where is customButton located now? is it a subview of self or `someSubView`?
self.customButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
// [self addSubview:customButton];
}
}
Thus, it is much easier for IB to say let's set this once, and let the programmer figure the rest out than for IB to try and track all changes made to an object and report them the to the UI.
viewDidLoad is called after your nib is loaded, and creating a new UITextField instance at this point will not be associated with your nib. If you're setting up new instances manually you also need to manually setup the delegates, and add them as subviews of your view.
The XIB file has no way of knowing that you are changing the reference. Consider the following piece of code
NSObject *myObjA = [[NSObject alloc]init]; //create object
NSObject *myObjB = myObjA; //assign reference <- this is the your case after xib load
myObjB = [[NSObject alloc]init]; //create object, myObjA still lives on.
It's basically the same that happens when you load your XIB file; You get the reference to the instantiated object (equals myObjB in above example). You can do with the reference what ever you please but you do not change the interface instance just by creating a new object.