I'm implementing a function to create a custom UIAlertView. I have extended UIView with a category, and I'm creating my method, called
- (void)showCustomAlertWithTitle:(NSString *)title andMessage:(NSString *)message
I want to add a selector to a button inside my custom alertview, but this selector is contained in my UIViewController. Should I move the selector into the category, or somehow reference it in the viewcontroller? And If I move the selector into the category, I won't be able to access the navigationcontroller...
in UIView+Category.m:
- (void)showCustomAlertWithTitle:(NSString *)title andMessage:(NSString *)message
{
UIView *alert = [[UIView alloc] initWithFrame:CGRectMake((self.frame.size.width/2)-100, (self.frame.size.height/2)-50, 200, 100)];
UIButton *confirmButton = [[UIButton alloc] initWithFrame:CGRectMake(30,40, 50, 50)];
[confirmButton addTarget:self action:#selector(delete:) forControlEvents:UIControlEventTouchUpInside];
[alert addSubview:confirmButton];
[self addSubview:alert];
}
selector method in MyViewController.m
- (IBAction)delete:(id)sender
{
[[self.view viewWithTag:-7] removeFromSuperview];
self.navigationController.navigationBar.hidden = NO;
}
You are bumping up against the limitations of categories. You can't add instance variables to a class in a category, so you don't have a clean way to save a pointer to your presenting view controller.
I would suggest adding a new method to your category:
- (void) addButtonWithTitle: (NSString*) title
target: (id) target
action: (SEL) action
forControlEvents: (UIControlEvents)controlEvents;
Then in the implementation of that method, use the parameters that are passed in to create and configure the button. The method includes a target, selector, and list of events that should trigger the action.
The selector should be implemented in your viewController , since you could be using your alertView in different viewControllers. Therefore sometimes you would need to perform a logic specific to that viewController. Also MVC 101 forbids you from trying to implement an action in a UIView subclass. Therefore again your viewController should implement the action.
Related
I am creating custom dialogs for my app and some what copying UIAlertController in some aspects. How should I implement the behaviour where when you click any action from alert/dialog the controller is dismissed.
How does Apple do it without making us manually specify for each action handler that it should dismiss the view controller?
I have like them one view controller class:
#interface MyAlertViewController : UIViewController
- (void)addAction:(MyAlertAction *) action;
//...
And one class for the actions:
#interface MyAlertAction : NSObject
- (instancetype)initWithTitle:(nullable NSString *)title handler:(void (^)(MyAlertAction *action))handler;
EDIT: How I did it taking in accord the answer feedback:
//MYAlertViewController.m
- (void)viewDidLoad {
for (int i = 0; i < self.actions.count; i++) {
MYAlertAction *action = self.actions[i];
button = [[UIButton alloc] initWithFrame:CGRectZero];
button.tag = i;//this here is how I link the button to the action
[button addTarget:self action:#selector(executeAction:) forControlEvents:UIControlEventTouchUpInside];
[actionStackView addArrangedSubview:button];
[self.actionsStackView addArrangedSubview:actionLayout];
}
}
- (void)executeAction:(UIButton *) sender{
[self dismissViewControllerAnimated:YES completion:^{
//this is where the button tag comes in handy
MYAlertAction *actionToExecute = self.actions[sender.tag];
actionToExecute.actionHandler();
}];
}
How does Apple do it without making us manually specify for each action handler that it should dismiss the view controller?
You are confusing two different things:
The UIAlertAction's last initialization parameter, the handler parameter, which you get to set from outside, and which is to run after the button is tapped and after the alert has been dismissed. It is a block.
The actual button's action, which the client can't set or see. It is configured by the alert controller. It is a selector.
So now, you play the role of the UIAlertController. In your
- (instancetype)initWithTitle:(nullable NSString *)title handler:(void (^)(MyAlertAction *action))handler;
the client hands you the first action I mentioned, the block, and you store it for later execution. But the second action, the button action, the selector, is entirely up to you as you create the button in response to this call.
So as you configure the button, just configure it with a target/action pair that calls into a method of your view controller, just as for any button. In method, when called, the view controller dismisses itself, and in the completion handler of the dismissal, calls the block.
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.
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.
This is part of my code inside UINavigationController subclass.
I've created a custom UIButton that will show most of the time.
How can I hide it in specific views?
I want to be able to setHidden the button inside some ViewControllers. The UIButton is a property.
-(void)viewDidLoad
{
[super viewDidLoad];
_coolBtn = [UIButton buttonWithType:UIButtonTypeCustom];
[_coolBtn setFrame:CGRectMake(0, 0, 56, 39)];
[_coolBtn setImage:[UIImage imageNamed:#"top.png"] forState:UIControlStateNormal];
[_coolBtn addTarget:self action:#selector(doSomethingCool) forControlEvents:UIControlEventTouchUpInside];
[self.navigationBar addSubview:_coolBtn];
}
Adding this inside the ViewDidLoad of the ViewController where I want to hide the button:
SubClassUInav *test =[[SubClassUInav alloc]init];
[test.coolBtn setHidden:YES];
Doesn't work.
Edit:
Maybe it's because I'm creating a new instance of it?
I'm not referencing to this subclass in my code. The only thing I did was to add it as a custom class inside the IB when the UINavigationController is selected.
Here is what you have to do.
In SubClassUINav.h:
#interface SubClassUInav : UINaviagationController {}
#property (nonatomic, strong) UIButton *coolBtn;
In SubClassUINav.m:
#synthesize _coolBtn = coolBtn;
In your MyViewController.m:
#import "SubClassUINav.h"
// get reference of your nav controller, do not create new instance by alloc-init
SubClassUINav *subClassUINavInstance = (SubClassUINav *) self.navigationController
[subClassUINavInstance.coolBtn setHidden: YES]; //Access your properties
Hope now you get a clear view.
you can also do it with using NotificationCenter like bellow
Add observer in NSNotificationCenter from button Define class:-
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(HideButton:)
name:#"HideButton"
object:nil];
-(void)HideButton:(NSNotification *)notification {
hide button code
}
Calling this using Bellow code:-
[[NSNotificationCenter defaultCenter] postNotificationName:#"HideButton" object:self];
What you are doing wrong is this line
SubClassUInav *test =[[SubClassUInav alloc]init];
This creates a new instance and in that instance the button state will be hidden.In your class somewhere you will be doing the same which is added as subview.Use that instance and make it hidden
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).