Adding views to the current viewcontroller from another class - ios

I have a UIViewController named MainViewController. I have an another class named ViewMaker.
In the ViewMaker class I have a function as follows.
-(UIView *)GetContentView
{
UIView *return_view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 500, 500)];
UIButton *done_button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
done_button.frame = CGRectMake(300, 300, 100, 50);
[done_button setTitle:#"Done" forState:UIControlStateNormal];
[return_view addSubview:done_button];
return return_view;
}
In the above function actually I will add more views like textviews, labels etc.
In the MainViewController class I am calling the above method as follows.
-(void)CreateViews
{
UIView *content_view = [[[ViewMaker alloc] init] GetContentView];
[self.view addSubview:content_view];
}
Now I have added a view with done button(and other components like textviews, labels etc) from the MainViewController class.
My problem is about adding a target function for the done button. When I click the done button I want to perform some actions(like add some views) in the MainViewController according to the datas in the view that was returned from the ViewMaker class.
How can I do this?
Thanks in advance.

I assume your ViewMaker class is same as PopupContentMaker,
add this to your done button:
[done_button addTarget:self action:#selector(doSomething) forControlEvents:UIControlEventTouchUpInside];
in PopupContenMaker:
- (void)doSomething{
if ([self.delegate respondsToSelector:#selector(handleDoneButtonTap)]) {
[self.delegate performSelector:#selector(handleDoneButtonTap) withObject:self];
}
}
declare a delegate property in your PopupContentMaker, and make MainViewController the delegate,
-(void)GetContentView
{
PopContentMaker *pcm = [[PopupContentMaker alloc] init];
pcm.delegate = self;
[self.view addSubview:[pcm GetContentView]];
}
-(void)handleDoneButtonTap{
//Do action after done button
}

You can try:
[done_button addTarget: self
action: #selector(buttonClickedMethod)
forControlEvents: UIControlEventTouchUpInside];
In this way you can call a custom method when button is pressed.

You can pass the target and action as parameters to the getContentView method or you can use the delegate pattern.

Related

Where should i define all my view objects?

Ok, so till now i have been declaring all my view objects in .h file. For e.g:
UIButton *btnCustomFacebookLogin;
And define in my .m file like:
btnCustomFacebookLogin = [UIButton buttonWithType:UIButtonTypeCustom];
btnCustomFacebookLogin.backgroundColor = [UIColor darkGrayColor];
btnCustomFacebookLogin.frame = CGRectMake(0,0,180,40);
btnCustomFacebookLogin.center = CGPointMake(self.view.center.x, 200);
[btnCustomFacebookLogin setTitle: #"Facebook Login" forState: UIControlStateNormal];
[btnCustomFacebookLogin addTarget:self action:#selector(facebookLoginButtonClicked) forControlEvents: UIControlEventTouchUpInside];
[self.view addSubview:btnCustomFacebookLogin];
And i have a few more buttons in my app which are all defined in my
- (void)viewDidLoad
I got this checked from my mentor and he told me that all these buttons should be in a separate method and not in viewDidLoad, i have no idea where they should be, i went through some sample codes on the internet and could not find a clue. What is the proper place where all the buttons are defined ? Since i work for a company now i have to follow what conventions are told to me.
Keep your IBOutlets properties outside of header file (*.h) and declare them inside implementation file (*.m) in interface extension of your View Controller.
In your implementation file (*.m) :
#interface MyViewController ()
#property (nonatomic, weak) IBOutlet UIButton *myButton;
#end
Remember to set your property behaviour nonatomic as by default each property gets atomic behaviour and when using UI objects there is no need to make them thread safe. Set it to weakas you are not a direct owner of the object, its lifecycle belongs to your storyboard (assuming you use storyboard).
Meet Doshi is correct about using viewDidLoad and performing selector from inside it, to make an initial setup for your UI elements. It makes your code clear and readable.
I would possibly call it:
- (void)styleUI {
//insert your code here
}
If you find yourself to set specific UIButton styling in different View Controllers in this same way, then to avoid boilerplate code you could create own Class Category for UIButton and specify this same style in one place.
Create a new file selecting Objective-C File.
In File type prefix for your project with name for class (example:
MAPStyling).
Select Category for File Type.
Select Class that you want to create a category for (for UIButton select UIButton).
Inside you category class in implementation file (*.m) add method for your styling:
- (void)map_applyFacebookStyle
// insert your code here but instead to name of your button refer to self.
self.title = #"Facebook Login";
}
Expose method in header file of your category by adding method signature into #interface section.
#interface UIButton (MAPStylig)
- (void)map_applyFacebookStyle;
#end
In your View Controller implementation file (*.m) import your Class Category and modify your method:
- (void)styleUI {
[btnCustomFacebookLogin map_applyFacebookStyle];
}
This will call #selector of your styling method from your Class Category on your UIButton. As you can imagine in this way you can remove a lot of boilerplate code from every View Controller in your application.
Your mentor says, "All these buttons should be in a separate method and not in viewDidLoad". Means you are setting frames and properties to all buttons into viewDidLoad method. He didn't said that you can't define all objects into .h file. So he says, you have do that into other method. Just use following code.
I think, right now you did this:-
- (void)viewDidLoad
{
btnCustomFacebookLogin = [UIButton buttonWithType:UIButtonTypeCustom];
btnCustomFacebookLogin.backgroundColor = [UIColor darkGrayColor];
btnCustomFacebookLogin.frame = CGRectMake(0,0,180,40);
btnCustomFacebookLogin.center = CGPointMake(self.view.center.x, 200);
[btnCustomFacebookLogin setTitle: #"Facebook Login" forState: UIControlStateNormal];
[btnCustomFacebookLogin addTarget:self action:#selector(facebookLoginButtonClicked) forControlEvents: UIControlEventTouchUpInside];
[self.view addSubview:btnCustomFacebookLogin];
}
So, just put this code into one method and call this method from viewDidLoad like this:-
- (void)viewDidLoad
{
[self settingPropertiesOfButtons];
}
- (void)settingPropertiesOfButtons
{
//insert your code here..
btnCustomFacebookLogin = [UIButton buttonWithType:UIButtonTypeCustom];
btnCustomFacebookLogin.backgroundColor = [UIColor darkGrayColor];
btnCustomFacebookLogin.frame = CGRectMake(0,0,180,40);
btnCustomFacebookLogin.center = CGPointMake(self.view.center.x, 200);
[btnCustomFacebookLogin setTitle: #"Facebook Login" forState: UIControlStateNormal];
[btnCustomFacebookLogin addTarget:self action:#selector(facebookLoginButtonClicked) forControlEvents: UIControlEventTouchUpInside];
[self.view addSubview:btnCustomFacebookLogin];
}
That's it. If you could framing those buttons in .storyboard file or in .xib, then its better.

Reusing variables in other method file

I have the following code to generate a UIBUtton from a method file called FirstViewController, since the location of the button will change in different secondviewController or thirdViewController, is it possible to set a variable for the location (CGRect) of the unbutton in the FirstViewController and change the CGRect Value in the second or third viewController?
In FirstViewController.m
-(void)method:(UIView *)_view {
UIButton*Touch1= [UIButton buttonWithType:UIButtonTypeRoundedRect];
[Touch1 addTarget:self action:#selector(TouchButton1:) forControlEvents:UIControlEventTouchUpInside];
[Touch1 setFrame:CGRectMake(50,50, 100, 100)];
**//I want to set a variable for the CGRectMake**
Touch1.translatesAutoresizingMaskIntoConstraints = YES;
[Touch1 setBackgroundImage:[UIImage imageNamed:#"1.png"] forState:UIControlStateNormal];
[Touch1 setExclusiveTouch:YES];
[_view addSubview:Touch1];
NSLog(#"test ");
}
In SecondViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
ViewController * ViewCon = [[ViewController alloc]init];
**//Change variable here by defining the new CGRect here.**
[ViewCon method:self.view];
}
This pattern is incorrect:
ViewController * ViewCon = [[ViewController alloc]init];
[ViewCon method:self.view];
as you are allocating a new view controller just to use one of its the method and then you are throwing the view controller instance away. It's extremely inefficient and inconvenient.
Either move the method to a utility class, as a class method:
[Utils method:self.view rect:rect];
or subclass UIViewController and implement the method in that base class and then derive all similar view controllers from that base class, passing any variables into it.
[self method:rect]; // method implemented in MyBaseViewController
You also asked this question before and accepted an answer that promotes the use of this bad pattern. That will mislead others into using this bad pattern.

How to write a code to do this job?

I use following code in VC1 :
ResultViewController *primaryView = [[ResultViewController alloc] initWithNibName:#"ResultView" bundle:nil];
UIImageView *profileView = [[UIImageView alloc] initWithImage:[UIImage imageNamed: #"profile.png"]];
[_coinView setPrimaryView: primaryView.view];
[_coinView setSecondaryView: profileView];
[_coinView setSpinTime:1.0];
than I have following code in a View file:
-(IBAction) flipTouched:(id)sender{
[UIView transitionFromView:(displayingPrimary ? self.primaryView : self.secondaryView)
toView:(displayingPrimary ? self.secondaryView : self.primaryView)
duration: _spinTime
options: UIViewAnimationOptionTransitionFlipFromLeft+UIViewAnimationOptionCurveEaseInOut
completion:^(BOOL finished) {
if (finished) {
displayingPrimary = !displayingPrimary;
}
}
}];
}
this code combo helps me to flip a view shown in VC1, it works well, but now what I am trying to do is to have a button shown in VC1, when it clicked, I can also flip the same view. So I guess the functional code should be written in VC1, and I just cannot figure out a good code to do that, anyone can help me please? BTW, how can I call "[self loadData]" of VC1 in view file?
It sounds like you just need to wire up flipTouched: to a UIButton:
UIButton *myButton = [[UIButton alloc] init]; // Or an outlet from your XIB/storyboard
[myButton addTarget:self
action:#selector(flipTouched:)
forControlEvents:UIControlEventTouchUpInside];
[_coinView flipTouched: nil ];
I find out this works good for me.

UITableViewCell skipped in responder chain

I'm attempting to trigger an event in a subview of a UITableViewCell, and let it bubble up the responder chain and be handled by a custom UITableViewCell subclass.
Basically:
SomeView.m (which is a subview of the UITableViewCell)
[self.button addTarget:nil action:#selector(someAction:) events:UIControlEventTouchUpInside]
SomeCustomCell.m
- (void)someAction:(id)sender {
NSLog(#"cool, the event bubbled up to the cell");
}
And to test why this wasn't working, I've added the someAction: method on the ViewController and the ViewController is the one that ends up handling the event that bubbles up from the table view cell subview, even though the Cell should handle it. I've checked that the Cell is on the responder chain and I've verified that any views on the responder chain both above and below the cell will respond to the event if they implement the someAction: method.
What the heck is going on here?
Here's a project that shows it https://github.com/keithnorm/ResponderChainTest Is this expected behavior somehow? I haven't found any documentation stating UITableViewCell's are treated any differently than other UIResponder's.
The cell seems to ask its table view for permission. To change that you can of course override
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
return [self respondsToSelector:action];
}
Swift 3, 4, 5:
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
return self.responds(to: action)
}
I've concluded that this is either a bug or undocumented intended behavior. At any rate, I ended up brute force fixing it by responding to the event in a subview and then manually propagating the message up the responder chain. Something like:
- (void)customEventFired:(id)sender {
UIResponder *nextResponder = self.nextResponder;
while (nextResponder) {
if ([nextResponder respondsToSelector:#selector(customEventFired:)]) {
[nextResponder performSelector:#selector(customEventFired:) withObject:sender];
break;
}
nextResponder = nextResponder.nextResponder;
}
}
I've also updated my demo project to show how I'm using this "fix" https://github.com/keithnorm/ResponderChainTest.
I still welcome any other ideas if anyone else figures this out, but this is the best I've got for now.
You can change the code in View.m as
[button addTarget:nil action:#selector(customEventFired:) forControlEvents:(1 << 24)];
to
[button addTarget:cell action:#selector(customEventFired:) forControlEvents:(1 << 24)];
do like this
#implementation ContentView
// uncomment this to see event caught by the cell's subview
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self)
{
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button setTitle:#"Click" forState:UIControlStateNormal];
[button setBackgroundColor:[UIColor blueColor]];
[button addTarget:self action:#selector(customEventFired:) forControlEvents:UIControlEventTouchUpInside];
button.frame = CGRectMake(4, 5, 100, 44);
[self addSubview:button];
}
return self;
}
- (void)customEventFired:(id)sender
{
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Event Triggered in cell subview" message:#"so far so good..." delegate:nil cancelButtonTitle:#"cancel" otherButtonTitles:nil, nil];
[alertView show];
}
#end
now customEventFired: method get called
I think this one is the easiest solution. If you do not specify a target the event will automatically bubble up the responder chain.
[[UIApplication sharedApplication]sendAction:#selector(customAction:) to:nil from:self forEvent:UIEventTypeTouches];

UIView and UIViewController

I know this is really basic stuff but i need to understand whether my understanding of this is correct.
So what i want to do is this. I want an view with a label on which when double tapped flips and loads another view. On the second view i want a UIPickerView and above i have a button saying back. Both views will be of same size as an UIPickerView which is 320px x 216px.
What i am thinking of to do is create two UIViewclasses named labelView and pickerView. I would then create a viewController which on loadView loads labelView then when user double taps the labelView i get an event in labelView class which is sent to my viewController that then can unload loadView and load the pickerView.
Does this sound as the best way to do this ? Is there a simpler way ? I am also unsure how i route the event from the labelView class to the viewControllerclass.
I dont exactly know the most efficient way to do it(as i am also now to this language),but it is for sure that i have solved ur problem. I made a simple program for that.Three classes involved here in my eg are BaseViewController (which will show two views),LabelView and PickerView (according to ur requirement).
In LabelView.h
#protocol LabelViewDelegate
-(void)didTapTwiceLabelView;
#end
#interface LabelView : UIView {
id <LabelViewDelegate> delegate;
}
#property(nonatomic,retain)id <LabelViewDelegate> delegate;
-(void)didTouch;
#end
In LabelView.m
#synthesize delegate;
-(id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self)
{
UILabel* labl = [[UILabel alloc] initWithFrame:CGRectMake(10, 5, frame.size.width-20,20)];
labl.text = #"Some Text";
[self addSubview:labl];
[labl release]; labl = nil;
self.backgroundColor = [UIColor grayColor];
UITapGestureRecognizer* ges = [[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(didTouch)] autorelease];
ges.numberOfTapsRequired = 2;
[self addGestureRecognizer:ges];
}
return self;
}
-(void)didTouch
{
[delegate didTapTwiceLabelView];
}
//=============================================================
In Pickerview.h
#protocol PickerViewDelegate
-(void)didTapBackButton;
#end
#interface PickerView : UIView <UIPickerViewDelegate,UIPickerViewDataSource>{
id <PickerViewDelegate> delegate;
}
#property(nonatomic,retain)id <PickerViewDelegate> delegate;
#end
In Pickerview.m
#implementation PickerView
#synthesize delegate;
-(id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self)
{
UIPickerView* picker = [[UIPickerView alloc] initWithFrame:CGRectMake(0, 30, 320, 216)];
picker.delegate = self;
picker.dataSource = self;
[self addSubview:picker];
[picker release]; picker = nil;
self.frame = CGRectMake(frame.origin.x, frame.origin.y, 320, 250);
UIButton* btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[btn setFrame:CGRectMake(10, 1, 50, 27)];
[btn setTitle:#"Back" forState:UIControlStateNormal];
[btn addTarget:self action:#selector(backButton) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:btn];
}
return self;
}
-(void)backButton
{
[delegate didTapBackButton];
}
//====================================================================
in BaseViewController.h
#import "LabelView.h"
#import "PickerView.h"
#interface VarticalLabel : UIViewController<UITextFieldDelegate,PickerViewDelegate,LabelViewDelegate> {
PickerView* myPickerView;
LabelView* myLabelView;
}
#end
In BaseViewController.m
-(void)viewDidLoad
{
[super viewDidLoad];
myPickerView= [[PickerView alloc] initWithFrame:CGRectMake(0, 50, 320, 250)];
[self.view addSubview:myPickerView];
myPickerView.delegate = self;
myLabelView= [[LabelView alloc] initWithFrame:CGRectMake(0, 50, 320, 250)];
[self.view addSubview:myLabelView];
myLabelView.delegate = self;
myPickerView.hidden = YES;
}
#pragma mark PickerViewDelgate
-(void)didTapBackButton
{
myPickerView.hidden = YES;
myLabelView.hidden = NO;
}
#pragma mark LabelViewDelegate
-(void)didTapTwiceLabelView
{
myPickerView.hidden = NO;
myLabelView.hidden = YES;
}
To get events from a button to the view controller, just hook up the button's event, e.g. touch up inside, to a method in the view controller, using interface builder. (Double tapping is probably more complicated though.)
When you say 'flips', do you mean it actually shows an animation of flipping over a view to show a 'reverse' side? Like in the weather app when you hit the 'i' button? I'm assuming this is what you mean.
Perhaps check TheElements sample example on the iPhone Reference Library, it has an example of flip animation.
Btw, it's not strictly necessary to unload the loadView that is being 'hidden' when you flip -- it saves you having to construct it again when you flip back -- but it may be pertinent if you have memory use concerns, and/or the system warns you about memory being low.
Also, what do you mean by "create a UIView"? Do you mean subclass UIView, or just instantiate a UIVIew and add children view objects to it? The latter is the usual strategy. Don't subclass UIView just because you want to add some things to a UIView.
If you've got one screen of information that gives way to another screen of information, you'd normally make them separate view controllers. So in your case you'd have one view controller with the label and upon receiving the input you want, you'd switch to the view controller composed of the UIPickerView and the button.
Supposing you use Interface Builder, you would probably have a top level XIB (which the normal project templates will have provided) that defines the app delegate and contains a reference to the initial view controller in a separate XIB (also supplied). In the separate XIB you'd probably want to add another view controller by reference (so, put it in, give it the class name but indicate that its description is contained in another file) and in that view controller put in the picker view and the button.
The point of loadView, as separate from the normal class init, is to facilitate naming and linking to an instance in one XIB while having the layout defined in another. View controllers are alloced and inited when something that has a reference to them is alloced and inited. But the view is only loaded when it is going to be presented, and may be unloaded and reloaded while the app is running (though not while it is showing). Generally speaking, views will be loaded when needed and unnecessary views will be unloaded upon a low memory warning. That's all automatic, even if you don't put anything in the XIBs and just create a view programmatically within loadView or as a result of viewDidLoad.
I've made that all sound more complicated than your solution, but it's actually simpler because of the amount you can do in Interface Builder, once you're past the curve of learning it. It may actually be worth jumping straight to the Xcode 4 beta, as it shakes things up quite a lot in this area and sites have reported that a gold master was seeded at one point, so is likely to become the official thing very soon.
With respect to catching the double tap, the easiest thing is a UITapGestureRecognizer (see here). You'd do something like:
// create a tap gesture recogniser, tell it to send events to this instance
// of this class, and to send them via the 'handleGesture:' message, which
// we'll implement below...
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(handleGesture:)];
// we want double taps
tapGestureRecognizer.numberOfTapsRequired = 2;
// attach the gesture recogniser to the view we want to catch taps on
[labelView addGestureRecognizer:tapGestureRecognizer];
// we have an owning reference to the recogniser but have now given it to
// the label. We don't intend to talk to it again without being prompted,
// so should relinquish ownership
[tapGestureRecognizer release];
/* ... elsewhere ... */
// the method we've nominated to receive gesture events
- (void)handleGesture:(UIGestureRecognizer *)gestureRecognizer
{
// could check 'gestureRecognizer' against tapGestureRecognizer above if
// we set the same message for multiple recognisers
// just make sure we're getting this because the gesture occurred
if(gestureRecognizer.state == UIGestureRecognizerStateRecognized)
{
// do something to present the other view
}
}
Gesture recognisers are available as of iOS 3.2 (which was for iPad only; so iOS 4.0 on iPhone and iPod Touch).

Resources