I have a question about memory management.
For example I have an iPhone application that uses multiply programmatically created views.
for example programmatically generated buttons.
UIButton *myButton=[UIButton alloc] initWithFrame:...; //etc
then, normally we add this button to subviews array:
[self.view addSubview:myButton];
then we releasing button.
[myButton release]
When I need to remove this button how can I keep track on this button in subviews array?
I know I can do this using tag property but I think exists another way to keep connection with it.
You can simply assign it to an instance variable:
UIButton *myButton = ...;
[self.view addSubView:myButton];
myInstanceVariable = myButton;
[myButton release];
You just need to be careful: as soon as you do something like [myInstanceVariable removeFromSuperview]; it might get deallocated immediately (if you haven't retained it) and it would then point to invalid memory.
You can try to declare somewhere a retain property of UIButton* type, that can be assigned with pointer value to your button instance:
#interface myclass
#property (retain, nonatomic) UIButton *savedButton;
#end
#implementation myclass
#synthesize savedButton;
- (void) someMethod...
{
...
UIButton *myButton=[UIButton alloc] initWithFrame:...;
[self.view addSubview:myButton];
self.savedButton = myButton;
[myButton release];
...
}
...
#end
Related
So please forgive me as I am very new to stackoverflow and to ios programming in general.
I am attempting to change the text of a button i have, replacing current text with a date string (dateStringForInput) I am passing in from another class. The button is called myTodayButton and is declared in my .h file for my class inputMilesViewController...
#property (strong, nonatomic) IBOutlet UIButton *myTodayButton;
Below is the code from my .m.
+(void) changeButtonText:(NSString*) dateStringForInput{
NSLog(#"we got to changebuttontext");
[_myTodayButton setTitle:dateStringForInput forState:UIControlStateNormal];
}
I am getting the following error "Instance variable "_myTodayButton" accessed in class method.
I understand that this is written in a class method rather than an instance method but i cant figure out how to get this change to happen. the button which prompts this call to changeButtonText is in the other class which is creating dateStringForInput.
Try like this...
For (+) instance.
+ (void) changeButtonText: Only have access to the self object.
--Step 1: Remove follwing line from header file
#property (strong, nonatomic) IBOutlet UIButton *myTodayButton;
--Step 2:
Add UIButton *myTodayButton in your class file globally..
That means declare before #implementation
Eg: In .m File
#import "Controller.h"
UIButton *myTodayButton // Add like this before #implementation
#implementation Controller
For (-) instance
- (void) changeButtonText: Only have access to the instance methods
Class methods do not have access to instance variables, that's why you're getting the error you're seeing.
You need to change the method to an instance method:
- (void)changeButtonText:(NSString *)dateStringForInput;
Then you will need to pass or obtain a reference to the instance of the class containing the method to the class calling the method.
I hope that makes sense... Add a comment if you need any clarification!
change method to an instance method instead of a class method ("-" instead of "+") create an instance of the class and then simply call it like so:
MyClass *classInstance = [[MyClass alloc] init];
[classInstance changeButtonText:#"stringText"];
Also its recommended to have weak properties for IBOutlets.
#property (nonatomic, weak) IBOutlet UIButton *myButton;
[EDIT]
Regarding your second issue with segues. Firstly do you really need a different view controller for simply displaying a UIDatePicker? I normally display them in the same UIViewController using an actionSheet, its a little work to get just right but solves this problem. I can post the code if you like?
Anyway if you want to keep your current setup I would do the following:
In the ViewController that contains the button create a new public NSString property in the .h file.
#property (nonatomic, strong) NSString *dateString;
In the View Controller that contains the date picker, after the user has selected the date override the perpareForSegue method. In here get the destination view controller (which is the one with the button and new string property created in point one above) and set the dateString.
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
MyViewController *todayViewController = (MyViewController*)[segue destinationViewController];
[todayViewController setDateString:#"valueYouRequire"];
}
In the ViewController with the button set the button's title in the viewDidLoad to be the dateString.
(void)viewDidLoad
{
[super viewDidLoad];
if (self.dateString) [self.myTodayButton setTitle:self.dateString forState:UIControlStateNormal];
}
[EDIT]
If you want to go down the actionSheet route then add this method to the ViewController with the button and play around with the frame sizes to fit your needs. The method below creates a date picker, adds it to an actionSheet and displays the actionSheet.
- (IBAction) datePressed:(id)sender {
if (!_actionSheet) {
_actionSheetFrame = CGRectMake(0, self.view.frame.size.height - 373, 320, 403);
_actionSheetBtnFrameOk = CGRectMake(20, 330, 280, 50);
_actionSheetBtnFreameCancel = CGRectMake(20, 275, 280, 50);
_pickerFrame = CGRectMake(0, 50, 320, 216);
_actionSheet = [[UIActionSheet alloc] initWithTitle:#"Select Date" delegate:self cancelButtonTitle:#"Cancel" destructiveButtonTitle:nil otherButtonTitles:#"OK", nil];
_datePicker = [[UIDatePicker alloc] init];
[_datePicker setDatePickerMode:UIDatePickerModeDate];
[_actionSheet addSubview:_datePicker];
}
[_datePicker setMaximumDate:[NSDate date]];
[_actionSheet showInView:self.view.superview];
[[[_actionSheet subviews] objectAtIndex:kActionBtnIndexOk] setFrame:_actionSheetBtnFrameOk];
[[[_actionSheet subviews] objectAtIndex:kActionBtnIndexCancel] setFrame:_actionSheetBtnFreameCancel];
[_actionSheet setFrame:_actionSheetFrame];
[_datePicker setFrame:_pickerFrame];
}
You will also have to implement the UIActionSheetDelegate method to set the button's text once the user has selected his desired date.
#pragma mark - UIActionSheet Delegate Methods
- (void) actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex {
if (buttonIndex == 0) {
[self.myTodayButton setTitle:self.dateStringFromSelectedDate forState:UIControlStateNormal];
}
}
having a little issue in an ARC environment. Creating an NSObject that adds a view to a parent view - it's basically a 'popup class' that can handle some text and display it.
In a view controller it's instantiated..
CBHintPopup *popup = [[CBHintPopup alloc]init];
[popup showPopupWithText:#"test text" inView:self.view];
And the actual class files..
CBHintPopup.h
#interface CBHintPopup : NSObject {
}
-(void)showPopupWithText:(NSString *)text inView:(UIView *)view;
-(IBAction)closePopup;
#property (nonatomic, strong) UIView *popupView;
#property (nonatomic, strong) UIImageView *blackImageView;
#property (nonatomic, strong) UIButton *closeButton;
#end
CBHintPopup.m
#implementation CBHintPopup
#synthesize popupView,blackImageView, closeButton;
-(void)showPopupWithText:(NSString *)text inView:(UIView *)view {
//CREATE CONTAINER VIEW
self.popupView = [[UIView alloc]initWithFrame:CGRectMake((view.frame.size.width/2)-(225/2),-146,225,146)];
self.popupView.alpha = 0;
self.popupView.backgroundColor = [UIColor clearColor];
//CREATE AND ADD BACKGROUND
UIImageView *popupBackground = [[UIImageView alloc]initWithFrame:CGRectMake(0,0,225,146)];
popupBackground.image = [UIImage imageNamed:#"hintbackground.png"];
[self.popupView addSubview:popupBackground];
//CREATE AND ADD BUTTON
self.closeButton = [UIButton buttonWithType:UIButtonTypeCustom];
[self.closeButton addTarget:self action:#selector(closePopup) forControlEvents:UIControlEventTouchUpInside];
[self.popupView addSubview:self.closeButton];
//CREATE AND ADD LABEL
UILabel *popupTextLabel = [[UILabel alloc]initWithFrame:CGRectMake(22,25,176,93)];
popupTextLabel.text = text;
[self.popupView addSubview:popupTextLabel];
[view addSubview:self.popupView];
}
-(void)closePopup {
NSLog(#"HI");
}
Recieving the following once closePopup is called via pressing the button ('HI' is not printed)..
-[CBHintPopup performSelector:withObject:withObject:]: message sent to deallocated instance 0x246b2fe0
I've tried retaining the button in non-ARC and a load of other methods but simply having no luck. Probably something real simple but i can't nail it. I've removed all the setting up of the labels and images etc to save some space, so ignore alpha's etc.
Any help will be much appreciated, thanks for your time.
Have you implemented the constructor for CBHintPopup,since you have called the constructor
[[CBHintPopup alloc]init];
you have to implement the constructor method like this
in .m file of CBHintPopup
-(id)init{
if(self == [super init]){
// do some initialization here
}
return self;
}
I tried your code and found the crash you mentioned. I found a solution for fixing the crash.
I declared the CBHintPopup *popup; in the viewController's interface. And changed this line
CBHintPopup *popup = [[CBHintPopup alloc]init];
to
popup = [[CBHintPopup alloc]init];
Everything worked fine for me. But I couldn't find the reason behind this. Hope this will help you.
Found a fix for it - instead of CBHintPopup being an NSObject, i simply made it a sub-class of UIView and added self to the parent view (instead of self.popupView). I wouldn't really call this a 'fix' though - more of an alternative method. Surely an NSObject can add a UIView (with a UIBUtton) to a parent view with no problems? Is this a bug?
Make sure you are retaining the object for class CBHintPopup.
I think the crash is coming because object of CBHintPopup deallocates. And hence the action method is not found.
I am very new to iPhone development. I am trying to disable an already existing button but I cant actually obtain a pointer to a specific element in the view. For instance, I have the following in the viewController header
- (IBAction)one:(id)sender;
and the implementation is
- (IBAction)one:(id)sender {
}
which are just event handlers. However, I need disable the button when view opens and I am a little bit lost on how to obtain references to elements outside of the event handler.
So in other words, my thought is to have something like:
UIButton* myButton = //something
where the something is where I am lost on what to do. Any ideas? I greatly appreciate any help I get on here!
You need to create a property for your button in the interface:
#property(nonatomic, retain) IBOutlet UIButton * button;
And add this to implementation:
#synthesize button;
Then connect the button to it in interface builder. After this you can disable the button by:
button.enabled = NO;
Hope I could help!
Just give tag to your button and access your button with tag value.
UIButton *btn = (UIButton*)[self.view viewWithTag:1];
[btn setHidden:YES];
In your .h File
#import <UIKit/UIKit.h>
#interface RpViewController : UIViewController
#property (retain , nonatomic)IBOutlet UIButton *Btn1;
#end
In your .m file , in implementation write this :
#synthesize Btn1;
Now on interface , click on button.
In button's properties - > Drawings - check Hidden checkbox.
Wherever you want to show that button , just write.
[Btn1 setHidden:FALSE];
#property (strong, nonatomic) UIButton *button;
#synthesize button;
// In View Did Load...
self.button = [UIButton buttonWithType:UIButtonTypeCustom]; // button can be of any type.
[self.button setTag:1];
// if you have more buttons initialize it and set its tag. you can get to know which button was pressed using tags.
[button addTarget:self action:#selector(buttonEvent:) forControlEvents:UIControlEventTouchUpInside];
-(void) buttonEvent:(UIButton *) sender
{
NSLog(#"%d",sender.tag);
if(sender.tag == 1)
{
[self.button setEnabled:NO]; // This makes your button disabled, i.e you can see the button but you cannot click on it.
[self.button setHidden:YES]; // This makes your button hidden.
}
}
if you have more doubts ping me back.
I have an IBOutletCollection of UIButtons:
#property (nonatomic, retain) IBOutletCollection(UIButton) NSMutableArray *Buttons;
with an ibaction i would to change the highlighted state permanently after the touch down event.
This Problem is very similar to this:
IBOutletCollection of UIButtons - changing selected state of buttons
... but with the for-loop the buttons doesnt change.
i also tried the perfomselector method from here: Keep iPhone UIButton Highlighted
but it doesnt work.
now my code:
-(IBAction)toggleButtons:(id)sender
{
NSUInteger Index = [button tag];
[[Buttons objectAtIndex:Index] setHighlighted:YES];
}
if i change line four to this:
[[Buttons objectAtIndex:3] setHighlighted:YES];
it works for the fourth element in my collection... But not with the index variable....
regards, phil
Update
SelectionViewController.h
#import <UIKit/UIKit.h>
#interface SelectionViewController : UIViewController
#property (nonatomic, retain) IBOutletCollection(UIButton) NSMutableArray *Buttons;
- (IBAction)toggleButtons:(id)sender;
#end
SelectionViewController.m
#import "SelectionViewController.h"
#interface SelectionViewController ()
#end
#implementation SelectionViewController
#synthesize Buttons;
-(IBAction)toggleButtons:(id)sender
{
UIButton *button = sender;
NSUInteger Index = [button tag];
[self performSelector:#selector(doHighlight:) withObject:sender afterDelay:0];
[[Buttons objectAtIndex:Index] setHighlighted:YES];
}
- (void)doHighlight:(UIButton *)b {
[b setHighlighted:YES];
}
Okey Update 2:
Now i had declared my Buttons as normal IBOutlet and this is not working:
-(IBAction)toggleButtons:(id)sender
{
UIButton *button = sender;
[button setHighlighted:YES];
}
But if change it to this:
-(IBAction)toggleButtons:(id)sender
{
[myOutletButton setHighlighted:YES]; //Normal Outlet
}
it works....
But why is not possible with the sender?
regards!
Update 3
This works also:
for(id button in self.view.subviews)
{
[button setHighlighted:YES];
}
Ok if change the delay time in the selector to 1, the state will be highlighted. I am using "touch down" event... i think after i touched up the button gets its old state. Which event is the right?
Given that your example works with a specific integer, the problem is probably that the tag property is not set properly for each of your buttons. If the buttons are created in interface builder, each of them will have a default tag value of 0. To check this, click on the button and then, in the Attributes Inspector, scroll down to View and see what value is entered in the tag field
I'd like to be able to create new UI elements (a UIProgressView bar) in a separate UIViewController every time a user taps on a button.
How would I go about doing this?
To create a UIProgressView programmatically, it is simply a matter of using alloc and init, setting a frame, and adding a subview, like so:
//.h
#import <UIKit/UIKit.h>
#interface ExampleViewController : UIViewController {
//create the ivar
UIProgressView *_progressView;
}
/*I like to back up my iVars with properties. If you aren't using ARC, use retain instead of strong*/
#property (nonatomic, strong) UIProgressView *progressView;
#end
//.m
#implementation
#synthesize progressView = _progressView;
-(void)viewDidLoad {
/* it isn't necessary to create the progressView here, after all you could call this code from any method you wanted to and it would still work*/
//allocate and initialize the progressView with the bar style
self.progressView = [[UIProgressView alloc]initWithProgressViewStyle:UIProgressViewStyleBar];
//add the progressView to our main view.
[self.view addSubview: self.progressView];
//if you ever want to remove it, call [self.progressView removeFromSuperView];
}
Read this guide https://developer.apple.com/library/ios/#DOCUMENTATION/WindowsViews/Conceptual/ViewPG_iPhoneOS/CreatingViews/CreatingViews.html
Most of the UI elements create like this:
UIView *view = [[UIView alloc] initWithFrame:CGRect];
// set the property of the view here
// ...
// finally add your view
[ViewController.view addSubView:view];