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.
Related
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.
I have 2 Custom View classes(CustomView_A, CustomView_B) derived from UIView. I have UIViewController that should be able to switch between views at run-time..
What so far, I have done is.. in the Storyboard, I am using CustomView_A class as the View class.
#interface MyViewController: UIViewController
#property (nonatomic, weak) CustomView_A *customView_A;
Now I have the second CustomView_B class and I want to change view of MyViewController's view to CustomView_B at run-time.
How can I do that? Thanks in advance..
okay, here is the code as you want -
in your MyViewController.h put -
#property (nonatomic, retain) CustomView_A *customView_A;
#property (nonatomic, retain) CustomView_B *customView_B;
-(void)switchView; // to switch the views.
in your MyViewController.m put -
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.customView_A = [[CustomView_A alloc]initWithFrame:self.view.frame];
self.customView_A.backgroundColor = [UIColor redColor];
UIButton *trigger = [UIButton buttonWithType:UIButtonTypeRoundedRect]; // Just take this button so that your switchView methods will get called on click of this method.
[trigger setFrame:CGRectMake(10, 10, 50, 30)];
[trigger setTitle:#"Click" forState:UIControlStateNormal];
[trigger addTarget:self action:#selector(switchView) forControlEvents:UIControlEventTouchUpInside];
[self.customView_A addSubview:trigger];
[self.view addSubview:self.customView_A];
self.customView_B = [[CustomView_B alloc]initWithFrame:self.view.frame];
self.customView_B.backgroundColor = [UIColor yellowColor];
self.customView_B.hidden = YES;
[self.view addSubview:self.customView_B];
}
- (void)switchView
{
[UIView animateWithDuration:10 delay:10 options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.customView_A.hidden = YES;
self.customView_B.hidden = NO;
} completion:nil];
}
Do opposite when you again want to switch the views.
Don't. What you're describing is an essential misunderstanding of UIViewController. Once a UIViewController instance has a view, that is its view forever.
If you want two different views then either:
Use two view controllers (for example, you can present view controller B and its view on top of view controller A and its view, using a modal segue), or
Make at least one of those views not be owned by a view controller: just place that view in front of the other view and later remove it again, at will.
Try this:
- (IBAction)changeView {
if (self.customView_A.hidden == YES) {
self.customView_A.hidden = NO;
self.customView_B.hidden = YES;
//You should use a UIView animation here to do this.
}
else {
self.customView_A.hidden = YES;
self.customView_B.hidden = NO;
//Same here
)
}
In your viewDidLoad add the view to CGRectZero
- (void)viewDidLoad {
self.customView_A = [[CustomView_A alloc]initWithFrame:CGRectZero];
[self.view addSubview:self.customView_A];
//do the same with the other custom view
}
Sorry if the code is a little faulty, I didn't use Xcode to type this up.
I am trying to change the text and the position of a UILabel from another class.
I have successfully managed to call my -changeLabel method, which is in my FirstViewController class, from my SecondViewController class. Here is the code I have used in my two classes:
SecondViewController:
#import "FirstViewController.h"
#interface SecondViewController ()
#property (nonatomic,strong) FirstViewController *firstViewController;
#end
#implementation SecondViewController
#synthesize firstViewController;
- (IBAction)button:(id)sender {
firstViewController = [[FirstViewController alloc] init];
[firstViewController changeLabel];
}
FirstViewController:
- (void)changeLabel {
NSLog(#"changeLabel is called!");
label.text = #"New text.";
label.frame = CGRectMake(10, 100, 150, 40);
NSLog(#"%#", label.text);
NSLog(#"%f", label.frame.size.width);
}
The weird thing is that the logger looks like this after pressing the "button" that calls the method:
"2013-12-30 19:24:50.303 MyApp[655:70b] changeLabel is called!"
"2013-12-30 19:24:50.305 MyApp[655:70b] New text."
"2013-12-30 19:24:50.308 MyApp[655:70b] 0.000000"
So it seems the label text change, but it doesn't show up on the screen. And the label width is logged as 0.0.. even though I just set it to 150.
Why is this happening? Am I not able to change frame variables from another class? Is there another way to do this?
IMPORTANT:
As the FirstViewController is the main view controller while the SecondViewController is a side menu, similar to the facebook app:
I want to be able to press a "button" on the SecondViewController(side menu) and call a method in the FirstViewController(main) that changes the position(frame) of a UILabel.
EDIT:
Here is how I created the UILabel:
label = [[UILabel alloc] init];
label.frame = CGRectMake(0, 0, 150, 40);
label.text = #"Text."
[self.view addSubview:label];
I think problem is this. You are calling method from new instance of FirstViewController.
Let assume
1. FirstViewController at stack[0].
2. SecondViewController at stack[1].
If you are navigating or moving from
FirstViewController->SecondViewController
In this case FirstViewController already in memory with some address 0x23ffff.
And in SecondViewController you are again creating new instance of FirstViewController which is point to another address '0x234jurhu`
- (IBAction)button:(id)sender {
firstViewController = [[FirstViewController alloc] init];
[firstViewController changeLabel];
}
Don't create new instance here.
You can use delegate or NSNotification concept for this.
How are you displaying FirstViewController?
Here is the issue:
firstViewController = [[FirstViewController alloc] init];
[firstViewController changeLabel];
You creating a new instance of FirstViewController and updating the label text. If your using these VC's in a navigation stack and you pop back to FirstViewController from SecondViewController, you won't see any label change because they are different instances of the class.
If your using FirstViewController as a childViewController of SecondViewController (with naming of them I don't think this what your doing), then in the - (IBAction)button:(id)sender method you don't need to instantiate a new instance of FirstViewController on each button press.
I have figured out a way to do this thanks to "#Gaurav Wadhwani" answer on this question: call method from other class (self issue).
I added this code in my FirstViewController.h:
+ (FirstViewController *)singletonInstance;
And then added this code in my FirstViewController.m
static FirstViewController *_singletonInstance = nil;
+(FirstViewController*)singletonInstance
{
#synchronized([FirstViewController class])
{
if (!_singletonInstance)
_singletonInstance = [[self alloc] init];
return _singletonInstance;
}
return nil;
}
+(id)alloc
{
#synchronized([FirstViewController class])
{
NSAssert(_singletonInstance == nil, #"Attempted to allocate a second instance of a singleton.");
_singletonInstance = [super alloc];
return _singletonInstance;
}
return nil;
}
Then I added this code in my SecondViewController to run the changeLabel method:
[[FirstViewController singletonInstance] changeLabel];
And that seems to work just fine so far. I hope it wont cause any other "problems" in the future, but right now it seems to be perfect.
Try doing this:
[label setNeedsDisplay];
After the last line in changeLabel.
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).