Subclasses of UIButton loaded from nibs are not initialized properly - iphone

I have a very simple subclass of UIButton:
#interface MyButton : UIButton
#end
#implementation MyButton
- (id) initWithCoder:(NSCoder *)decoder
{
if (!(self = [super initWithCoder:decoder]))
return nil;
NSLog(#"-[%# initWithCoder:%#]", self, decoder);
return self;
}
#end
In Interface Builder I add a UIButton, set its button type to Rounded Rect and its class identity to MyButton.
When running, I have the following log:
-[<MyButton: 0x5b23970; baseClass = UIButton; frame = (103 242; 114 37); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x5b23a90>> initWithCoder:<UINibDecoder: 0x6819200>]
but the button is not a round rect button anymore.
Observed on both iOS 3.2 and iOS 4.
Is this a bug or am I missing something obvious?
Create an instance of MyButton programmatically is not an acceptable answer, thanks.

Programmatically, you instantiate a button with +[UIButton buttonWithType:] which is actually a factory that returns a subclass of UIButton. So if you derive from UIButton you actually don't derive from your round rect button class (UIRoundedRectButton) but from a generic button class. But you are not allowed to subclass from UIRoundedRectButton AFAIK since it's an internal class.
It seems to be problematic to derive from UIButton, I've seen a lot of people recommed to derive from UIControl instead and implement the drawing yourself.
But you might find these articles helpful:
How to override -drawrect in UIButton subclass?
http://www.cocoabuilder.com/archive/cocoa/284622-how-to-subclass-uibutton.html
http://www.cimgf.com/2010/01/28/fun-with-uibuttons-and-core-animation-layers/
Also, I don't know why you want to derive from UIButton, but if you want to do some customization that does not involve overwriting any other methods it might be helpful to use the fact that you can do something like this:
- (id)initWithCoder:(NSCoder *)decoder {
// Decode the frame
CGRect decodedFrame = ...;
[self release];
self = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[self setFrame:decodedFrame];
// Do the custom setup to the button
return self;
}

I’m not sure if this is acceptable for your needs, but I tend to prefer to override -awakeFromNib instead of -initWithCoder: in these circumstances. Does doing this resolve the issue you’re seeing?

Related

Apply PanGesture to uiview in another class with action

I have a ViewController iDragHomeViewController
and another
NSObject class iDrag
"iDragHomeViewController.m"
- (void)viewDidLoad
{
[super viewDidLoad];
UIView *dragView = [[UIView alloc]initWithFrame:CGRectMake(100, 100, 200, 200)];
[dragView setBackgroundColor:[UIColor greenColor]];
[self.view addSubview:dragView];
iDrag *drag = [[iDrag alloc]init];
[drag makeDraggableView:dragView];
}
"iDrag.m"
-(void)makeDraggableView: (UIView *)dragView {
UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc]initWithTarget:self action:#selector(cellPan:)];
[dragView addGestureRecognizer:panRecognizer];
}
- (void)cellPan:(UIPanGestureRecognizer *)iRecognizer {
UIView *viewToDrag = [[UIView alloc]init];
viewToDrag = iRecognizer.view;
CGPoint translation = [iRecognizer translationInView:[viewToDrag superview]];
viewToDrag.center = CGPointMake(iRecognizer.view.center.x + translation.x,
iRecognizer.view.center.y + translation.y);
[iRecognizer setTranslation:CGPointMake(0, 0) inView:[viewToDrag superview]];
}
Now what I am trying here is to make this "dragView"(belongs to iDragHomeViewController) draggable in iDrag class by applying it PanGesture.
But the code is crashing.
I know some people will suggest me to use NSNotification to handle Pan action in another class but I dont want to write a single line in iDragHomeViewController and handle everything in iDrag Class only.
Is it Possible ??
Please Help.
To be sure I need to know the error output but a guess...
From UIGestureRecognizer doc:
- (id)initWithTarget:(id)target action:(SEL)action
target parameter:
An object that is the recipient of action messages sent by the receiver when it recognizes a gesture. nil is not a valid value.
Thats the reason why your app crashes. The drag object is already released while the recognizer tries to call the cellPan: method.
You initialize the iDrag object in viewDidLoad and is not retained. (It is not a member variable and not used anywhere else....). End of the viewDidLoad the iDrag object is released by ARC.
I would not make any other object responsible for handling pan gestures, unless I had a good reason for that. And would make the view controller responsible for creating gesture recognizer and handling the events.
I assume you have really good reason for that, like the handling is used by multiple views, etc... If it is the case then a better approach would be making iDrag object singleton(shared instance).
Got the answer
Just need to declare iDrag object as a property
#property(nonatomic,strong) iDrag *drag;

objective-c: Delegate object argument getting overwritten when i create multiple instances of custom class

EDIT: I apologize for wasting time, the erorr had nothing to do with what I'm taking about but rather some logic in my code that made me believe this was the cause. I'm awarding Kevin with the correct answer since using his idea to pass the whole AuthorSelectionView, and his note on correcting the NSNumer mistake. Sorry about that.
I've been trying to figure this out for hours, and even left it alone for a day, and still can not figure it out...
My situation is as follows:
I've created a custom class that implements 'UIView' and made this class into a protocol as follows:
custom UIView h file
#protocol AuthorSelectionViewDelegate <NSObject>
-(void)AuthorSelected:(NSNumber *)sender;
#end
#import <UIKit/UIKit.h>
#interface AuthorSelectionView : UIView
#property (nonatomic,assign) id<AuthorSelectionViewDelegate> delegate;
#property (strong,retain) NSNumber *authorID;
- (id)initWithFrame:(CGRect)frame withImage:(UIImage *)img withLabel:(NSString *)lbl withID:(int)authorID ;
#end
the implementation...
- (id)initWithFrame:(CGRect)frame withImage:(UIImage *)img withLabel:(NSString *)lbl withID:(int)authorID
{
self = [super initWithFrame:frame];
if (self) {
self.authorID = [[NSNumber alloc] initWithInt:authorID]; //used to distinguish multiple instances of this class in a view.
...
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, FRAMEWIDTH, FRAMEHEIGHT)];
[button addTarget:self action:#selector(CUSTOMBUTTONCLICK) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:button];
}
return self;
}
- (void) CUSTOMBUTTONCLICK
{
[self.delegate performSelector:#selector(AuthorSelected:) withObject:self.authorID];
}
Now the method in my delegate object gets called just fine, but my major problem here is that something is going on with the object being pass through when i have multiple instances of the AuthorSelected class alloc'd.. (the NSNumber authorID). I'm getting some weird behavior with it. It seems almost random with the value being passed, but i'm detecting some pattern where the value passed through is coming up late..
thats confusing so ill try to explain:
I create two instances of the AuthorSelected view, one with authorID=1 and the other with authorID=2.
On the first press, lets say i press the first button, i'll get 1 as expected.
On the second press, if I press the 1st custom button, i'll get '1', but if i press the second i'll still get 1.
On the third go, either button will give me back '2'
I feel like this is some issue with pointers since that has always been a weak point for me, but any help would be greatly appreciated as I can not seem to figure this one out.
Thank you!
EDIT:
as requested here is how I create the AuthorSelectionView Objects...
AuthorSelectionView * asView01 = [[AuthorSelectionView alloc]
initWithFrame:CGRectMake(0, 0, FRAMEWIDTH, FRAMEHEIGHT)
withImage:userPic1
withLabel:randomUserName
withID:1];
asView01.delegate = self;
AuthorSelectionView * asView02 = [[AuthorSelectionView alloc]
initWithFrame:CGRectMake(0, 0, FRAMEWIDTH, FRAMEHEIGHT)
withImage:userPic2
withLabel:randomUserName2
withID:2];
asView02.delegate = self;
A detail that may be important:
As soon as i click on one of these custom views, my code is set to (for now) call the method that runs the above AuthorSelectionView alloc code, so that i can refresh the screen with the same layout, but with different userpic/userName. This is poor design, I know, but for now I just want the basic features to work, and will then worry about redrawing. I metion this tidbit, becuase I understand that objective-c 'layers' veiws on top of eachother like paint on a canvas, and had a thought that maybe when I click what I think may be my 2nd button, its really 'clicking' the layer beneath and pulling incorrect info.
Your description of the problem is a bit confusing, but this line in your init is very clearly wrong:
self.authorID = [self.authorID initWithInt:authorID];
In -init, your property self.authorID defaults to nil, so the expression [self.authorID initWithInt:authorID] is equivalent to [nil initWithInt:authorID], which evaluates back to nil. So you should actually be seeing nil in your action. You probably meant to say self.authorID = [NSNumber numberWithInt:authorID]
You're missing the alloc message, so this message:
self.authorID = [self.authorID initWithInt:authorID];
Is sent to a nil target, because self.authorID hasn't been allocated yet.
So first allocate it, then use the init method, or mix these two messages. A faster syntax allows to do it this way:
self.authorID= #(authorID);
EDIT
I don't see where you initialize the delegate, that method shouldn't even be called if you haven't initialized it. Show the code where you create the AuthorSelectionView objects and set the delegates.
instead of :
self.authorID = [self.authorID initWithInt:authorID];
put :
self.authorID = [NSNumber numberWithInt:authorID];
or
self.authorID = [[NSNumber alloc] initWithInt:authorID];
EDIT :
Don't you have errors or warnings in your code ? I can't see you returning self object in the init method ("return self;")

How to Use UIGesterRecognizer within Subclass

I have a subclass:
CustomView : UIScrollView.
Inside of this subclass I have some methods that, say, populate my custom view with some UI elements. I want to add UIGesterRecognizer functionality to these elements but I do not know how to handle setting the delegate and adding selectors:
#implementation CustomView
-populateMe{
UIImageView *iv = [...];
UIGesterRecognizer r = [UIGesterRecognizer alloc]
initWithTarget:self
action:#selector(handleMySwipe:);
//<==where to declare handler
r.delegate = self; //<==COMPILER ERROR self
[iv.addGestureRecognizer r];
}
So my problem is where I commented above: self is not a valid delegate (I tried self.superclass) and where do I need to declare a handler for action, i.e. handleMySwipe.
Please explain so I understand.
CustomView is potentially a delegate, if you implement the UIGestureRecognizerDelegate protocol for it. That is, implement a few methods within your CustomView class that the protocol requires you to.
Once you've done that, you should be able to set the target parameter to self without any errors. Since your target is now self, you need to implement the selector/method handleMySwipe: within your CustomView class because that is where it will look for it (the target).
- (void) handleSwipe:(UIGestureRecognizer *)gr {
}

How do I set a custom font for the whole application?

Is there any way to How to Apply global font [new custom font] to whole application in iphone objective-c.
I know that we can use below method to set font for each label
[self.titleLabel setFont:[UIFont fontWithName:#"FONOT_NAME" size:FONT_SIZE]];
But I want to change for whole application.
Please help me if anyone know.
Apparently to change ALL UILabels altogether you will need to setup a category on UILabel and change the default font. So here's a solution for you:
Create a file CustomFontLabel.h
#interface UILabel(changeFont)
- (void)awakeFromNib;
-(id)initWithFrame:(CGRect)frame;
#end
Create a file CustomFontLabel.m
#implementation UILabel(changeFont)
- (void)awakeFromNib
{
[super awakeFromNib];
[self setFont:[UIFont fontWithName:#"Zapfino" size:12.0]];
}
-(id)initWithFrame:(CGRect)frame
{
id result = [super initWithFrame:frame];
if (result) {
[self setFont:[UIFont fontWithName:#"Zapfino" size:12.0]];
}
return result;
}
#end
Now ... in any view controller you want these custom font labels, just include at the top:
#import "CustomFontLabel.h"
That's all - good luck
Ican's solution with category might be prefered just to save the day. But avoid using category to override existing methods as apple explains:
Avoid Category Method Name Clashes
... If the name of a method declared in a category is the same as a method in the original class, or a method in another category on the same class (or even a superclass), the behavior is undefined as to which method implementation is used at runtime. ...
Note also that overriding -(id) init; would be safer than overriding -(id)initWithFrame:(CGRect)frame. You would not face with the problem of not receiving touch events when clicking on a label on UIButtons.
Is this what you mean?
#interface GlobalMethods
+(UIFont *)appFont;
#end
#implementation GlobalMethods
+(UIFont *)appFont{
return [UIFont fontWithName:#"someFontName" size:someFontSize];
}
#end
...
[self.titleLabel setFont:[GlobalMethods appFont]];
In case you want to do it somehow automatically (without using setFont on each control), I don't believe it's possible.
If you can limit your application – or this particular feature – to iOS 5, there’s a new API coming that lets you skin the default UI very conveniently. I can’t give you details, since they are still under NDA at the time I am writing this. Take a look at iOS 5 beta SDK to find out more.
CustomLabel.h
#import <UIKit/UIKit.h>
#interface VVLabel : UILabel
#end
CustomLabel.m
#import "CustomLabel.h"
#define FontDefaultName #"YourFontName"
#implementation VVLabel
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder: aDecoder];
if (self) {
// Initialization code
// Static font size
self.font = [UIFont fontWithName:FontDefaultName size:17];
// If you want dynamic font size (Get font size from storyboard / From XIB then put below line)
self.font = [UIFont fontWithName:FontDefaultName size:self.font.pointSize];
}
return self;
}

Where does the init *really* happen in a UIView placed in Interface builder?

I have a subclass of UIView called SlideOut. I want to capture the IB placement of the view when it loads, so I have this in my implementation:
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
IBframe.origin.x = frame.origin.x;
IBframe.origin.y = frame.origin.y;
IBframe.size.height = frame.size.height;
IBframe.size.width = frame.size.width;
// Initialization code.
NSLog(#"init if self x: %f, y: %f", IBframe.origin.x, IBframe.origin.y);
}
NSLog(#"init x: %f, y: %f", IBframe.origin.x, IBframe.origin.y);
return self;
}
... with a matching prototype in the .h file.
Upon starting up the simulator, I get all my other diagnostics, but neither of these log messages get called. So how does the instance actually get initialized? Or am I missing something? The position function works, but then fails because I haven't captured the actual frame of the thing. In any case, that's how I know I made the IB connections correctly.
Take a look here: Subclassing UIView, "Methods to Override", from UIView Class Reference.
In particular:
initWithCoder: - Implement this method
if you load your view from an
Interface Builder nib file and your
view requires custom initialization.
Objects that are loaded from an xib are actually unarchived. Thus, you should be using the initWithCoder: method. Alternatively, you may way to look at awakeFromNib instead.
I believe the function that gets called when the view is loaded from a NIB is initWithCoder not initWithFrame