I have these two properties defined in my appDelegate:
#property (nonatomic, strong) UIView * mainView;
#property (nonatomic, strong) MyCustomClass * customObj
What is the best way of adding subviews to mainView from code inside customObj?
I am not providing any sample code because (a) my code is terrible and (b) I just want to understand the best approach of doing this, so I can learn from this in the future, rather than having one solution to one specific problem.
Many thanks.
well, what about creating a method in MyCustomClass like this:
-(void)addSubViewToView:(UIView *)view
{
[view addSubview:otherView];
}
And then call it like this:
[customObj addSubViewToView:mainView];
It depends on what kind of class MyCustomClass is. Is it responsible for building mainView's view hierarchy? Then I'd inject a reference of mainView to customObj, like this:
customObj = [[MyCustomClass alloc] initWithView:mainView];
In this scenario, customObj would be some kind of builder object, that creates the view hierarchy inside mainView. Then I'd use the addSubView: selector inside MyCustomClass:
-(id)initWithView:(UIView*)view{
if(self = [super init]){
[view addSubView: ...];
[view addSubView: ...];
[view addSubView: ...];
}
}
Related
Im curious, where is the best option to allocate/init, set attributes of views (uibutton, uilabel, uitextfield, initializing variables, etc).
This is in regards to developing an app strictly programatically. I see some cases where these views have been allocated/init in the class -init method, but then other times i see other views set in the -loadview method.
Can anyone provide some clarity about this? And maybe some abstract examples of when the best time to do it for either method would be.
Thanks
The -init* family of functions would be a good place to initialize simple properties, e.g. strings, numbers, and the like. The initializer runs just after the memory for the object is allocated, and if you have something that can be initialized there then you should do it there.
For UIViewController instances, you probably have to wait until the nib has been loaded before you can initialize everything else. If you've got images that need to be placed inside subviews, or fonts that need configuring, or whatever, then you need to have the nib loaded first. -viewDidLoad is the best place for that stuff.
For UIView instances (or subclasses like UITableViewCell), you need to wait for the nib to be loaded too. You can use -awakeFromNib in that case.
Here's a quick comment on this:
-SubClass a UIView, smash all your UI elements into that view, well as many as you can at least. Import this subclassed view's header into your view controller's implementation file
-In your view controller, typecast your view controller's view like so:
-(HHYSignUpViewFirstPhase*)contentView
{
return (id)[self view];
}
-Invoke the loadView method
-(void)loadView
{
[self setView:[HHYSignUpViewFirstPhase new]];
}
-In your viewdidLoad, you can now set handlers to buttons and such from your subclassed UIView by calling to "[self contentView]" like so:
-(void)viewDidLoad
{
[super viewDidLoad];
[self setTitles:#"Sign Up"];
[[[self contentView] nameField] setDelegate:self];
[[[self contentView] emailField] setDelegate:self];
[[[self contentView] passwordField] setDelegate:self];
[[[self contentView] signupButton] addTarget:self action:#selector(signupPressed) forControlEvents:UIControlEventTouchUpInside];
}
Now you have it all set up, you just need to add methods to handle events from the button, for example in the view did load from your subview that you subclassed:
-(void)signupPressed
{
///do work
}
UIVIew subclass:
HHYSignUpViewFirstPhase.h
#interface HHYSignUpViewFirstPhase : UIView
#property (nonatomic) UIButton * signupButton;
#property (nonatomic) UITextField * emailField;
#property (nonatomic) UITextField * nameField;
#property (nonatomic) UITextField * passwordField;
#end
HHYSignUpViewFirstPhase.m
#import "HHYSignUpViewFirstPhase.h"
#implementation HHYSignUpViewFirstPhase
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self == nil)
return nil;
//do work, set up buttons, constraints, etc, etc.
return self;
}
#end
Essentially, what I'm saying here is that in the subclassed UIView you can initialize the UIView and set up all its constraints and EVERYTHING, frames included and then in the load view method of your UIViewController, you then call to this view and typcast the view of the UIViewController. So, sometimes you do the set up in the init, sometimes you do it in the load view, it depends on what you are trying to do, but this is how you set this up in a pure programmatic fashion with separation of duties, encapsulation, and all tied together in an MVC framework -- all work is separated into classes, and all controllers control a single class.
http://matthewmorey.com/creating-uiviews-programmatically-with-auto-layout/
and this
https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/ViewLoadingandUnloading/ViewLoadingandUnloading.html#//apple_ref/doc/uid/TP40007457-CH10-SW36
I am an iOS noob.
I am having a problem similar to this:
"self" object for UIViewcontroller has #"0 objects" in debug window in xcode
Re-starting xcode does not solve the problem.
I can see the self object in the view controller after a segue from my main view controller. I have a UITableViewController inside that view controller, with a separate class for delagete/data source for the table. Once i get into this tableviewcontroller code, the self object shows "0 objects" in the debugger. I believe that I am initializing the table view incorrectly to cause this problem somehow, but not quite sure how.
Thanks for the help. My code is below.
#implementation ConnectTableController
#synthesize perpArray;
#synthesize nameArray;
+ (ConnectTableController *)connectTableController
{
return [[ConnectTableController alloc] init];
}
- (void)viewDidLoad {
[super viewDidLoad];
perpArray = [[NSMutableArray alloc] init];
nameArray = [[NSMutableArray alloc] init];
}
....
#interface ConnectTableController : UITableViewController <UITableViewDelegate, UITableViewDataSource> {
UITableView *tableView;
NSMutableArray* perpArray;
NSMutableArray* nameArray;
}
+ (ConnectTableController*) connectTableController;
#property (nonatomic, strong) NSMutableArray *perpArray;
#property (nonatomic, strong) NSMutableArray *nameArray;
...
// Parent view controller code
#implementation ConnectVC
- (void)viewDidLoad {
[super viewDidLoad];
// Set the controller delegate/dataSource for the Table
connectTableController = [[ConnectTableController alloc] init];
connectTable.dataSource = connectTableController;
connectTable.delegate = connectTableController;
[self addChildViewController:connectTableController];
}
...
#interface ConnectVC : UIViewController{
IBOutlet UIButton *CONNECT;
ConnectTableController *connectTableController;
IBOutlet UITableView *connectTable;
}
The following applies to UITableViewControllers as well, make sure you are accessing the views properly
"Self" for a viewController doesn't contain the views, you have to check "self.view.subViews", "Self.view.subViews" contains the views for the viewController, not just "self" alone.
for example, if Self is a UIViewController then the following will crash your app with the corresponding error:
for(UIView * a in self)
{
NSLog(#"%#", a.description);
}
error:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIViewController countByEnumeratingWithState:objects:count:]: unrecognized selector sent to instance 0x7f854176fbf0'
It crashes because "self" of uiviewController doesn't contain the views
BUT if you try this, then you will have something to work with:
for (UIView *s in self.view.subviews) {
NSLog(#"%#", s);
}
this will log to console all the views in the UIViewController, thereby allowing you to access the views
Also, it looks like you may be trying to access the views of "connectTableController". To access this controller's views then here's how it works:
connectTableController.view.subViews.
UITableViewController * aaa = [UITableViewController new];
aaa.tableView;
aaa.view;
aaa.view.subviews;
So, this means you are going to access the following:
connectTableController.tableView
connectTableController.view.subViews
and then from there, you can do this:
connectTableController.tableView.subViews
.. that is of course if the subviews you are looking for are subviews of the UITableViewController's tableView
In addition, I think the way you are doing the placement of a UITableViewController is OKAY, but probably not the best choice, only because it gets a little complicated and it's not as easy as adding it as a child like that in order to make this work. You should be using something like this:
tester2 = [UITableViewController new];
[self addChildViewController:tester2];
[self.view addSubview:tester2.view];
[tester2 didMoveToParentViewController:self];
then of course you should combine this method when/if you want to remove the child view controller
- (void)hideContentController:(UIViewController*) content
{
[content willMoveToParentViewController:nil];
[content.view removeFromSuperview];
[content removeFromParentViewController];
}
This will hopefully give you a start, so also try this:
- (void)viewDidLoad {
[super viewDidLoad];
// Set the controller delegate/dataSource for the Table
connectTableController = [[ConnectTableController alloc] init];
connectTable.dataSource = connectTableController;
connectTable.delegate = connectTableController;
[self addChildViewController:connectTableController];
[self.view addSubview:connectTableController.view];
[connectTableController didMoveToParentViewController:self];
}
Also, I don't know that you need to do anything with delegation declaration since the child view controller is basically it's own little world within a world. Try it with and without these lines:
connectTable.dataSource = connectTableController;
connectTable.delegate = connectTableController;
What I mean, is possibly declare these in the UITableViewController instance or make sure you are adding the "connectTable to the the UITableViewController, something like this:
cconnectTableController.tableView = connectTable;
or
[cconnectTableController setTableView:connectTable];
then you can try this
connectTable.dataSource = cconnectTableController;
etc, etc.
Try this for loop and list the results
for (id s in self.view.subviews) {
NSLog(#"%#", s);
}
I've managed to setup a custom UIView class with a nib.
My .h looks like
#interface MyView : UIView <UITextFieldDelegate>
#property (nonatomic, weak) IBOutlet UITextField *textField;
#property (nonatomic, strong) MyView *topView;
And .m
#implementation MyView
NSString *_detail;
-(id)initWithCoder:(NSCoder *)aDecoder{
if ((self = [super initWithCoder:aDecoder])&&self.subviews.count==0){
MyView *v = [[[NSBundle mainBundle] loadNibNamed:#"MyView" owner:self options:nil] objectAtIndex:0];
self.textField = v.textField;
if (self.topView == nil)self.topView = self;
v.topView = self.topView;
[self addSubview:v];
}
return self;
}
-(NSString *)topDetail{
return _detail;
}
-(NSString *)detail{
return [self.topView topDetail];
}
-(void)setTopDetail:(NSString *)detail{
_detail = detail;
}
-(void)setDetail:(NSString *)detail{
[self.topView setTopDetail:detail];
}
- (BOOL)textFieldShouldReturn{
//here I show an UIAlertView using self.detail for the message
}
Note: The setup I have works exactly how I want it to.
The problem
What I would like to do is remove my manual detail methods and turn NSString *_detail into #property (...)NSString *detail
When I try it with the #property, then within my ViewController if i call
myView.detail = someString, myView will be referring to the top most view. Then if textFieldShouldReturn gets called because of user interaction, then it calls the nested MyViews _detail which has not been set.
What I want:
To not have to write extra code for access to _detail regardless of where I'm accessing it from. I want to merely declare the property and go on with my usual coding.
Your problem is that you're trying to keep the a class reference, topView, with an object property.
In other words every objects' topView is the object itself, which makes no sense.
Your definition should be:
#interface MyView : UIView <UITextFieldDelegate>
// Class "properties"
+ (instancetype)topview;
+ (void)setTopView:(UIView *)topView;
// Object properties
#property (nonatomic, weak) IBOutlet UITextField *textField;
#property (nonatomic, strong) NSString *detail;
Now you can keep track of the topView:
static MyView * _topView;
#implementation MyView
+ (instancetype)topView {return _topView}; // You could also create one here lazily
+ (void)setTopView:(UIView *)topView { _topView = topView };
-(id)initWithCoder:(NSCoder *)aDecoder{
if ((self = [super initWithCoder:aDecoder])&&self.subviews.count==0){
JUITextFieldHint *v = [[[NSBundle mainBundle] loadNibNamed:#"JUITextFieldHint" owner:self options:nil] objectAtIndex:0];
self.textField = v.textField;
if ([MyView topView] == nil)[MyView setTopView:self];
v.topView = self.topView;
[self addSubview:v];
}
return self;
}
No more need for manual setters and getters. Now you can use your detail property, either with anyInstance.detail or [MyView topView].detail, or even MyView.topView.detail if you like dots like me ;)
You're init method still looks weird but should work. Check Apples init template.
Lastly, textField can be weak as long as it has a superview, otherwise make it strong.
My xib contained one UIView (no controller). I had the UIView set to MyView for the class.
I changed the UIView back to just UIView then set File's Owner to MyView. This solved issues of recursion (which is why I had such a weird setup in the first place) and caused my variables and IBOutlets to be linked up properly.
Credit goes to How do I create a custom iOS view class and instantiate multiple copies of it (in IB)? and some of the comments which I missed the first couple times I read through it.
I have a UIViewController with its UIView which contains a UIButton. I want to trigger a method in UIViewController on button click event.
Keeping reference of UIViewController doesn't seem to be a good idea like the following link says:
Get to UIViewController from UIView?
So I want to achive this using a delegate. Any hint on how to achieve this?
You can do something like this
CustomView.h
#import <UIKit/UIKit.h>
#protocol CustomViewDelegate <NSObject>
-(void)didButtonPressed;
#end
#interface CustomView : UIView
#property (assign) id<CustomViewDelegate> delegate;
#end
CustomView.m
#import "CustomView.h"
#implementation CustomView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.backgroundColor = [UIColor whiteColor];
//[self addSubview:titleLbl];
UIButton *button= [UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame = CGRectMake(100, 100, 100, 50);
[button addTarget:self.delegate action:#selector(didButtonPressed) forControlEvents:UIControlEventTouchUpInside];
[button setTitle:#"pressMe" forState:UIControlStateNormal];
[self addSubview:button];
}
return self;
}
in your ViewController.m
-(void)loadView
{
[super loadView];
CustomView *view = [[CustomView alloc]initWithFrame:self.view.bounds];
view.delegate = self;
[self.view addSubview:view];
}
This is what the responder chain was built for. When you add a target to your button, just supply nil for the target:
[mySpecialButton addTarget:nil
action:#selector(mySpecialButtonTapped:)
forControlEvents:UIControlEventTouchUpInside];
The nil target basically means "send mySpecialButtonTapped: to any object in the responder chain that can handle it".
Now you can handle this selector anywhere in the responder chain, which includes the button itself, its containing view, its containing view controller, the UIApplication, and finally your AppDelegate. Just place this method in the object most appropriate for your needs:
- (void)mySpecialButtonTapped:(id)sender {
NSLog("My special button was tapped!");
}
You don't need delegates or callback blocks (as in the accepted answer) if you just want to bubble a message up.
I guess that you expected something more fundamental then just pass some button action to controller.
I always follow MVC pattern in case of model/view/controller collaboration. It resolve your issue and many other. And I want to share my experience.
Separate controller from view and model: don't put all of the "business logic" into view-related classes; this makes the code very unusable. Make controller classes to host this code, but ensure that the controller classes don't make too many assumptions about the presentation.
Define callback APIs with #protocol, using #optional if not all the methods are required.
For view define protocol like <view class name>Protocol (example NewsViewProtocol). For controller define delegate like <view class name>Delegate (example NewsViewDelegate) and dataSource like <view class name>DataSource (example NewsViewDataSource). Keep all this #protocols in one separate file named <view class name>Protocol.h (example NewsViewProtocol.h)
Short example:
Contents of NewsView.h
//
// NewsView.h
#interface NewsView : UIView <NewsViewProtocol> {
#protected
NSObject* delegate_;
NSObject* dataSource_;
}
#end
Contents of NewsController.h and .m
//
// NewsController.h
#interface NewsController : UIViewController <NewsViewDataSource, NewsViewDelegate> {
}
#property (nonatomic, weak) UIView<NewsViewProtocol>* customView;
#end
#implementation NewsController
- (void)viewDidLoad {
[super viewDidLoad];
self.customView = (UIView<NewsViewProtocol>*)self.view;
[self.customView setDelegate:self];
[self.customView setDataSource:self];
}
#end
Contents of NewsViewProtocol.h
//
// NewsViewProtocol.h
#protocol NewsViewProtocol;
#protocol NewsViewDelegate<NSObject>
#optional
- (void)someAction;
- (void)newsView:(UIView<NewsViewProtocol>*)newsView didSelectItemAtIndexPath:(NSIndexPath *)indexPath;
#end
#protocol NewsViewDataSource<NSObject>
#required
- (id)newsView:(UIView<NewsViewProtocol>*)newsView itemAtIndexPath:(NSIndexPath *)indexPath;
- (NSInteger)numberOfItemsInNewsView:(UIView<NewsViewProtocol>*)newsView section:(NSInteger)section;
- (BOOL)newsView:(UIView<NewsViewProtocol>*)newsView shouldDisplaySection:(NSInteger)section;
#end
#protocol NewsViewProtocol<NSObject>
#required
//Never retain delegate instance into implementation of this method
- (void)setDelegate:(NSObject<NewsViewDelegate>*)delegate;
//Never retain delegate instance into implementation of this method
- (void)setDataSource:(NSObject<NewsViewDataSource>*)dataSource;
- (void)reload;
#end
You may consider that it is redundant. In simple view controller, YES. But if you develop very complex screen with huge amount of data then it gives you some advantages as:
Helps you to separate responsibility between view and controller.
Keeps your code clear.
Makes you code more reusable.
Life is easy in xCode.
At the very beginning be sure that your xib View (the one with your button inside it) is associated to the right ViewController class. Which can be the default ViewController class that comes with a new project or your custom one.
After this, here comes the magic trick! Separate your view into 2 panel. The goal is to see your xib and your viewController code (the .m file). Now press the control key of your keyboard and drag your UIButton to the code. Select IBAction. It will generate something you can call a "listener" in other language. Go to the core code of your View Controller and complete the method!
Easy as that! Have fun :)
You don't really need delegates for this - it is how UIButtons are intended to be used. Just control-click and drag from your button to the .m file for your UIViewController. This will create a new method. From there, you can either make a call to the method you wrote or just copy-paste what you have into the new method.
You can try this:
[yourButton addTarget:self action:#selector(yourButtonAction:) forControlEvents:UIControlEventTouchUpInside];
And in your selector specify the action
- (IBAction)yourButtonAction:(id)sender {
//Action to perform
}
To add a button programmatically, in myViewController.m
UIView *yourView = [[UIView alloc] init];
UIButton *yourButton = [[UIButton alloc] initWithFrame:CGRectMake(0,0,100,21)];
[yourButton addTarget:self action:#selector(yourMethod) forControlEvents:UIControlEventTouchDown];
[yourView addSubview:yourButton];
More info here.
The title is what I think I need but i will go back one step. I want to create a class which handles certain things in an iOS app. This class might be called by multiple UIViewcontrollers in an iOS app. The class may need to show a UIView at some stage for user input. So my question is how can I show a UIView when I don't know which subclass of UIViewController is calling it? To what can I add the UIView from this class?
I suppose there are two possible answers either the class finds the current UIViewController or the calling subclass of UIViewController passes itself to the class so the class knows.
How is this supposed to be done.
Thanks guys for your help.
I'm going to expand on #ericleaf's comment regarding using a protocol and subclasses. It sounds like you are asking the following:
How can I create a resusable, generic class that presents a view
within a UIViewController subclass?
A great way to do this is to define a protocol in your generic class and have your view controller subclasses support this protocol. The protocol defines an interface for your custom class to comunicate with it's delegate, in this case a UIViewController subclass. Other than the protocol, the objects don't need to know anything else about the implementation of each other.
Any information your custom object needs to be able to present views within it's delegate would be passed via protocol methods. The specifics of the protocol are up to you based on your needs. You could have the custom object "ask" the delegate for information (e.g. what view should I put a subview in?) or you could have the protocol provide information to the delegate and let the delegate deal with it (e.g. here is a subview you can put wherever you want).
There is a lot of great documentation on protocols available on SO and elsewhere. This is long enough already so I kept the example fairly simple.
custom class .h file with protocol definition
// my custom class that adds adds a view to a view controller that supports it's protocol
// forward class definition for the protocol
#class MyAwesomeObject;
#protocol MyAweseomeObjectDelegate <NSObject>
- (UIView *)viewForMyAwesomeObject:(MyAwesomeObject *)awesomeObject;
#end
// this could be defined such that the delegate *must* be a UIViewController. I've left it generic.
#interface MyAwesomeClassObject : NSObject
#property (nonatomic, weak) id <MyAwesomeObjectDelegate> delegate;
#end
custom class .m file
// MyAwesomeObject.m
#import "MyAwesomeObject.h"
#implementation MyAwesomeObject
// this is a dumb example, but shows how to get the view from the delegate
// and add a subview to it
- (void)presentViewInDelegate
{
UIView *containingView = [self.delegate viewForMyAwesomeObject:self];
if (containingView) {
UIView *subview = [[UIView alloc] initWithFrame:containingView.bounds];
subview.backgroundColor = [UIColor redColor];
subview.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
[containingView addSubview:subview];
}
}
MyViewController .h using the custom object
// MyViewController.h
#import "MyAwesomeObject.h"
#interface MyViewController : UIViewController <MyAwesomeObjectDelegate>
#property (nonatomic, strong) MyAwesomeObject *awesomeObject;
#end
MyViewController .m using the custom object
// MyViewController.m
#import "MyViewController.h"
#implementation MyViewController
- (void)init
{
self = [super init];
if (self) {
_awesomeObject = [[MyAwesomeObject alloc] init];
_awesomeObject.delegate = self;
}
return self;
}
// MyAwesomeObjectDelegate
- (UIView *)viewForMyAwesomeObject:(MyAwesomeObject *)awesomeObject
{
return self.view;
}
You can get the class into a string and do a compare.
For example, lets assume your custom UIViewController subclass is CustomViewCon and the UIViewController object reference is myUnknownClassObject, then:
NSString *classString = NSStringFromClass([myUnknownClassObject class]);
Then you can:
if([classString isEqualToString:#"CustomViewCon"]){
//do something like maybe present a particular view
myUnknownClassObject.view = myCustomView; //or anything..
}
Similarly you can check for any class.
Edit: According to the suggestions from comments, you could also do the following(better way):
if([[myUnknownClassObject class] isKindOfClass:[CustomViewCon class]]){
//same as before
}
Why wont you use a block for this?
BaseViewController.h:
#property (copy) void (^addViewBlock)();
- (IBAction)showViewWhenNeeded;
BaseViewController.m:
- (IBAction)showViewWhenNeeded
{
if (self.addViewBlock)
self.addViewBlock();
}
And in your child class, set that block's actions, and call the method when you feel like you should put up a view.
ChildViewController.m
// within some method, propably init or smth
[self setAddViewBlock:^{
[self.vied addSubView:...];
}];
// when need to actually add the view
[self showViewWhenNeeded];