Adding NSObject to view - UIButton selector in NSObject causes crash - iphone

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.

Related

How to add a UIImage with UIGesture to most views in an application.

I want to make an Advertising banner in my app. A bit like iAd's.
I was going to make it by having a UIImage on the view then assigning the banner image. I would then add a touch gesture so the user could click it and go to another view in my app. I Know That I can do this on one view quite easily but I want this to be on most views in the app. Whats the best way for adding the banner to more than one view with out writing the same code more that once?
The below design shows the sort of banner im after.
Thanks
#import <UIKit/UIKit.h>
#class custom;
#protocol adDelegate
- (void)viewAd:(NSString *)adRate;
#end
#interface custom : UIView
#property (strong, nonatomic) UIImage *viewImage;
#property (assign) id <adDelegate> delegate;
#end
// Main class
#import "custom.h"
#implementation custom
#synthesize viewImage;
#synthesize delegate;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
UIImageView *imageView = [[UIImageView alloc] initWithFrame:frame];
imageView.image = viewImage;
[self addSubview:imageView];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
if ((self = [super initWithCoder:aDecoder]))
{
}
return self;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.delegate viewAd:#"view"];
}
You can Create a UIView Class and call it BannerView for instance.
// in the bannerView.h
#import <UIKit/UIKit.h>
#interface BannerView : UIView{
UIImageView* bannerImage;
}
#property(nonatomic,retain) UIImageView* bannerImage;
#end
//in the bannerView.m
#import "BannerView.h"
#implementation BannerView
#synthesize bannerImage;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
bannerImage=[[UIImageView alloc]initWithImage:[UIImage imageNamed:#"banner-image.png"]];
bannerImage.frame=CGRectMake(0, 0, 320, 100);
[self addSubview:bannerImage];
// add a uibutton on top of the uiimageview and assign an action for it
// better than creating an action recogniser
UIButton* actionButton=[UIButton buttonWithType:UIButtonTypeCustom];
actionButton.frame=CGRectMake(0, 0, 320, 100);
[actionButton addTarget:self action:#selector(yourAction) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:actionButton];
}
-(void) yourAction{
// do what ever here like going to an other uiviewController as you mentionned
}
#end
Now you can call this view from any View Controller this way
BannerView* banner=[[BannerView alloc] initWithFrame:CGRectMake(0, 300, 320, 100)];
[self.view addSubview:banner];
Try creating a parent class from UIView where you do all the display and handling of the banner using your UIImageView and gesture recognizers. Then whichever views need this functionality, derive them from this parent class, and override default handling in method so that you can customize the behavior in your child class.
A few suggestions:
First, why not just use a UIButton instead of a UIImage with a Gesture? All you're really doing is replicating button functionality after all...
Second, I'd probably tackle the overall problem by creating a class that includes the UIButton, like so:
#interface YourSuperViewController : UIViewController
#property (nonatomic, strong) IBOutlet UIButton *adButton;
- (IBAction)adTouched:(id)sender;
#end
In the viewDidLoad for this class, create the button, and add it to the view, and add your ad-specific logic to the adTouched action.
Then create the rest of the views in your app as an instance of YourSuperViewController. Like so:
#interface SomeOtherViewController : YourSuperViewController
Now the SomeOtherViewController will auto-magically have the ad button and respond to a touch on it properly. Done!
What everyone else has said is the best way. If you need custom functionality, subclassing is probably the way to go.
I just wanted to add one pedantic thing. Its important to remember that a UIImage is not a view. There has never been a UIImage on the screen, ever. A UIImage is a model object. It is just a collection of data. A UIImageView is a view object and as such, a UIImageView can display itself on the screen.
This might seem overly pedantic and nitpicky, but its important to have these things sorted out in our heads in order to effectively use MVC (model, view, controller)

"Instance variable 'xxx' accessed in class method....why?

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];
}
}

Delegate Being Sent To Wrong Class

I subclassed my navigation bar, making the title view clickable. When clicked, it will present another view controller. I am creating a protocol in the navigation bar, that will tell the navigation controller that the title view has been clicked. Here is how my navigation bar is defined:
NavigationBar.h:
#protocol NavigationBarDelegate;
#interface NavigationBar : UINavigationBar
{
id <NavigationBarDelegate> delegate;
BOOL _titleClicked;
}
#property (nonatomic, assign) id <NavigationBarDelegate> delegate;
#end
#protocol NavigationBarDelegate
#optional
- (void)titleButtonClicked:(BOOL)titleClicked;
#end
The delegate implements one optional method. The .m file is as follows:
NavigationBar.m:
#implementation NavigationBar
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
_titleClicked = 0;
}
return self;
}
- (void)drawRect:(CGRect)rect
{
self.tintColor = [UIColor colorWithRed:(111/255.f) green:(158/255.f) blue:(54/255.f) alpha:(255/255.f)];
UIImage *image = [UIImage imageNamed:#"titlelogo.png"];
UIButton *titleButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, image.size.width, image.size.height)];
titleButton.backgroundColor = [UIColor colorWithPatternImage:image];
[titleButton addTarget:self action:#selector(titleButton:) forControlEvents:UIControlEventTouchUpInside];
// self.navigationController.delegate = self;
[self.topItem setTitleView:titleButton];
[super drawRect:rect];
}
- (void)titleButton:(UIButton *)sender
{
_titleClicked = !_titleClicked;
[self.delegate titleButtonClicked:_titleClicked];
}
This creates a navbar with a logo and calls the titleButton method when the title button has been clicked. Everything is fine up till here and the navigation bar displays nicely.
In my RootViewController:
NavigationBar *navigationBar = [[NavigationBar alloc] initWithFrame:CGRectMake(0.0f, 0.0f, self.view.frame.size.width, 44.0f)];
navigationBar.delegate = self;
[self.navigationController setValue:navigationBar forKey:#"navigationBar"];
An implementation of titleButtonClicked is also there. When I click on the title view however, I get the following error: -[UINavigationController titleButtonClicked:]: unrecognized selector sent to instance
Why am I getting titleButtonClicked sent to UINavigationController? Is there something I need to do in my navigation controller? I am just using plain old UINavigationController. Do I need to subclass that too? If so, why?
EDIT:
Calling po self.delegate on line [self.delegate titleViewClicked:_titleClicked]; in NavigationBar.m yields the result below. How did the delegate change its type? How can I fix that?
(lldb) po self.delegate
(objc_object *) $1 = 0x07550170 <UINavigationController: 0x7550170>
As #idz said, the problem is with your:
#property (nonatomic, assign) delegete;
Don't you see that it's weird that you don't even have a:
#synthesize delegete;
That's because UINavigationBar already defines a delegate variable as idz said.
change your declaration to:
// use unsafe_unretained in ARC, not assign
#property (nonatomic, unsafe_unretained) myDelegete;
and of course
#synthesize myDelegate;
You have a clash/ambiguity between your delegate and UINavigationBar's delegate property. Rename your delegate to disambiguate them.

iphone. making UITextView but won't make it visible

#interface SomeViewController : UIViewController <UITextViewDelegate>
#property (retain, nonatomic) IBOutlet UIImageView *imageView;
#property (nonatomic, retain) UIImage *image;
#property (nonatomic, retain) IBOutlet UITextView *textView;
I need textview(editable) in my view controller.
so I set protocol ,
and also, IB to the file's owner, too. (I don't know, is it unnecessary?)
And my .m file.... viewDidLoad..
_textView = [[UITextView alloc]init];
[_textView setFrame:CGRectMake(8, 110, 304, 82)];
[_textView setFont:[UIFont boldSystemFontOfSize:12]];
_textView.editable = YES;
//[textView setText:#"hdgffgsfgsfgds"];// if uncommented, this actually visible...
[self.view insertSubview:_textView atIndex:2];
So far, it's working. But I want these methods to run...
- (void)textViewDidChange:(UITextView *)textView
{
if([_textView.text length] == 0)
{
_textView.text = #"Foobar placeholder";
_textView.textColor = [UIColor lightGrayColor];
_textView.tag = 0;
}
}
But after build, my textview is still empty.
What did I wrong?
You need to add the delegate to your textview, try with this:
_textView.delegate = self;
On the other hand,
textViewDidChange
Will only work when you change your text, if you want to place a text in the textview, you can do:
_textView.text = #"Foobar placeholder";
_textView.textColor = [UIColor lightGrayColor];
_textView.tag = 0;
In your ViewdidLoad
When synthesizing the textView, do you do it like #synthesize textView = _textView so that you can use the underscored name? Usually the underscored one is used only in the setter/getter methods (for instance when overriding them).
Note that
The text view calls this method in response to user-initiated changes to the text. This method is not called in response to programmatically initiated changes.
This is from Apple's documentation for the method you are using. This means if you are changing the text through the code, the method will not get fired.
Are you expecting [textView setText:#"hdgffgsfgsfgds"] to in turn invoke textViewDidChange:? Calling setText will not automatically invoke the textViewDidChange:.

How to add properties/methods to UIButton

I need to add two additional properties (NSString * and NSMutableArray *) along with three extra methods to UIButton. I also want to reference the new objects using a supertype if that is possible. I do not necessarily want to subclass (as I read that it is tricky and not recommended), but I am quite new to Objective-C and iOS development and don't know what else to do.
I tried to subclass UIButton with my subclass implementing a formal protocol in the following way:
#interface Button : UIButton <MyProtocol> ...
However, I found out this doesn't work like I thought it would, as buttonWithType: returns an object from a subclass. What else can I do to achieve the desired result?
-- EDIT:
Ok, my current code is like this:
#interface Button : UIButton <SteapeObject> {
ActionQueue * actions;
Meta meta;
}
#property (nonatomic, retain) ActionQueue * actions;
#property (nonatomic) Meta meta;
- (id) initWithFrame:(CGRect)frame;
...
And the implementation:
- (id) initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
NSLog (#"finally");
}
return self;
}
An still doesn't work. It seems that when I invoke:
Button * button = [Button buttonWithType: UIButtonTypeRoundedRect];
NSLog (#"%#", [button description]);
I should get two 'finally' strings and two descriptions in the log. However, I only get the two description strings:
[Session started at 2011-02-24 09:47:14 +0100.]
2011-02-24 09:47:15.431 IphoneClient3[702:207] <UIRoundedRectButton: 0x5f47690; frame = (0 0; 0 0); opaque = NO; layer = <CALayer: 0x5f47240>>
2011-02-24 09:47:15.461 IphoneClient3[702:207] <UIRoundedRectButton: 0x6a0f000; frame = (0 0; 0 0); opaque = NO; layer = <CALayer: 0x6a344b0>>
And you can see that the type is still UIRoundedRectButton, but the buttons do not respond to my added methods. Actually, since my overriden initWithFrame doesn't get called, that is to be expected. Perhaps I should default to implementing a custom control...
As far as I know, the doc does not say subclassing of UIButton is not recommended.
I have done it multiple times to add custom properties — without problems.
The only thing to do is to create the button using:
[Button buttonWithType:UIButtonTypeCustom]; // won't work for other button types though
Use a Category.
#interface UIButton (MyButtonCategory)
- (void) myMethod;
#end
#implementation UIButton (MyButtonCategory)
- (void) myMethod
{
NSLog(#"Called myMethod!");
}
#end
[EDIT]
Alternatively, if I finally understand you, you can do this.
#interface MyButton : UIButton
- (id) initWithFrame:(CGRect)rect;
#end
#implementation MyButton
- (id) initWithFrame:(CGRect)rect
{
if ((self = [super initWithFrame:rect])){
// Do your init in here
}
return self;
}
#end
Then calling
MyButton *btn = [MyButton buttonWithType:UIButtonTypeRoundedRect];
Should get you what you want. buttonWithType should call initWithFrame on your subclass.
I found that it is not possible to accomplish that task with the current implementation of the SDK.
Categories might help. Implement like this:
//In the UIButtonMyExtras.h file
#interface UIButton(MyExtras)
//extras
#end
//In the UIButtonMyExtras.m file
#implementation UIButton(MyExtras)
//extra implementation
#end
This adds these extras to every UIButton in your project.