Manual positioning of titleLabel in a custom UIButton (using layoutSubviews) - iphone

i've create a UIButton subclass for my app and i need to manually reposition the titleLabel to be about 1/4 of the button's height. this post; http://forums.macrumors.com/showthread.php?t=763140 appears to directly address the same issue, and the solution is simple and perfectly understandable. however, i have failed to implement this because my override of layoutSubviews is never called. my button is static, so layoutSubviews only need be called the first time, but it never is. my code:
#interface MyButton : UIButton {}
#implementation MyButton
- (id)initWithFrame:(CGRect)frame
{
self = [[UIButton buttonWithType:UIButtonTypeRoundedRect] retain];
[[self layer] setCornerRadius:14.0f];
[[self layer] setBorderWidth:3.0f];
[[self layer] setMasksToBounds:YES];
[self setBackgroundColor:[UIColor redColor]];
[self setFrame:frame];
return self;
}
- (void)layoutSubviews
{
NSLog(#"layout subs\n");
[super layoutSubviews];
}
#end
moving the label would be no problem, but layoutSubviews is never called. i've also tried adding layoutIfNeeded, but it made no difference. what's even weirder, is i've tried to call [self layoutSubviews] directly from the constructor but layoutSubviews is still not called!. i'm starting to think this might even be a bug in SDK 3.1.3
can anyone help?!

In layoutSubviews, you can do layout changes to your subviews. Make sure that inside the method:
√ Call [super layoutSubviews]
x Do not change value or rect of self.frame here. If you do so, it would recursively call layoutSubviews.
√ Make sure you only change frame of its subviews.
From UIView Class Reference

to answer my own question, it seems i was subclassing incorrectly. taking the following approach results in layoutSubviews being called correctly:
+ (id)buttonWithFrame:(CGRect)frame {
return [[[self alloc] initWithFrame:frame] autorelease];
}
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame])
{
...
[self setFrame:frame];
}
return self;
}
hope that helps others

Related

setNeedsDisplay does not always call drawRect

I've seen this question posted before but I cannot find the answer..
As expected, drawRect gets called automatically when the program launches. However when I call [self setNeedsDisplay] drawRect does not get called anymore and I cannot understand the reason why...
//DrawView.m
[self setNeedsDisplay];
-(void)drawRect:(CGRect)rect{
NSLog(# "Called_1");
//DRAW A PATH
}
Maybe using self is incorrect. I have also tried using DrawImage but still it doesn't work.
//DrawView.h
#interface DrawView : UIView {
UIImageView *drawImage;
}
//DrawView.m
-(id)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
drawImage = [[UIImageView alloc] initWithImage:nil];
drawImage.frame = CGRectMake( 0, 0, self.frame.size.width, self.frame.size.height );
[self addSubview:drawImage];
}
return self;
}
Usually when you want to have a view redraw you should call:
[self setNeedsDisplay: YES];
Granted I have never build on iOS, on OSX this code works every time. Also, for example, if you want your delegate to call a redraw for a view named someView:
[someView setNeedsDisplay: YES];
Note: that YES is a macro defined by obj-c that is just a value of 1.

CALayer/UIView crashes when I touch the UIVIew

Iam using a custom layer for drawing on my UIView. And to invalidate and update the layer I need to set my view to be the delegate of my CALayer. When this is done it draws my text correctly but when the view receives touches the app crashes.
What I am a doing wrong?
- (void) layoutSubView {
if (drawLayer == nil) {
drawLayer = [[CALayer alloc] init];
[drawLayer setDelegate:self];
[[self layer] addSublayer:drawLayer];
}
[drawLayer setFrame:[self bounds]];
[drawLayer setNeedsDisplay];
}
Above is my code to setup the CALayer, and below is the overridden drawLayer method.
- (void) drawLayer:(CALayer *)layer
inContext:(CGContextRef)ctx {
if (layer == drawLayer) {
UIGraphicsPushContext(ctx);
[self drawTitle:ctx];
UIGraphicsPopContext();
}
}
Here is the stack.
You can't use a UIView object to be the delegate of a layer other than its own layer. Its stated here. You will need to assign some other object as its delegate.

Overlay View with Subview, don't know calling VC

I need a view (infoView) to be displayed as an overlay on top of another view. As this infoView should be callable from every view of the app(e.g. introView), I'd like the code to be in the infoViews VC and just call its methods when an action at the currentView (introView) happens. I can't use push and pop, as I need to change the background color (infoView) and especially the alpha of the calling view (introView), so right now I do it with insertSubview.
My Code by now:
introVC .h
- (IBAction) openInf:(id)sender;
IBOutlet InfoVC *infoScreenVC;
introVC .m
- (IBAction) openInf:(id)sender {
[infoScreenVC openInfoMethod];}
infoVC .h
- (IBAction) closeInfoPressed;
- (void) openInfoMethod;
- (void) closeInfoMethod;
infoVC .m
- (IBAction) closeInfoPressed {
[self closeInfoPressed];}
- (void) closeInfoMethod {
[self.view removeFromSuperview];
[self.xx.view setAlpha:1.0f];}
- (void) openInfoMethod {
self.view.backgroundColor = [UIColor clearColor];
[self.xx.view setAlpha:0.2f];
[((MyAppAppDelegate *)[UIApplication sharedApplication].delegate).window
insertSubview: self.infoScreenVC.view aboveSubview: self.xx.view];}
When I push the button to show the infoView, my NSLogs tell me the method was called, but I can see the Subview wasn't added. I have absolutely no clue what to insert where right now it says xx in my code, as a VC reference from intro doesn't show me the screen.
If I put that code in introVC an modify it, it does show the infoView, calls the correct method to close, but again can't close (when I'm in introVC). I can't figure out how to tell my app who was the calling VC to get back there.
At some point, when all the code was in introVC I managed to even remove the Subview, but couldn't set the Alpha of introVC back to one.
I do struggle with that since two days.. -.- Or is there maybe an easier solution even?
Thank you very much!
//Edit after sergios answer:
intro.m
- (IBAction) openInf:(id)sender {
introViewController *introVC;
[infoScreenVC openInfoMethod:];}
info.h
- (void) openInfoMethod:(introViewController *introVC);
info.m
- (void) openInfoMethod:(introViewController *introVC) { //error occurs here
self.view.backgroundColor = [UIColor clearColor];
[self.introVC.view setAlpha:0.2f];
[((MyAppAppDelegate *)[UIApplication sharedApplication].delegate).window
insertSubview: self.infoScreenVC.view aboveSubview: self.introVC.view];}
and the occurring error says
Expected ')' before 'introVC'
I'm not sure how to pass the VC reference properly.
Thank you for your help!!
//EDIT Working Code:
As it works now, I'd like to sum things up:
- I give the calling VC (introVC) to openInfoMethod on the Action openInf like [infoVC openInfoMethod:introVC].
In openInfoMethod I "save" the calling VC in a local variable of type introVC (?) and add the overlay etc.
When the Action of the infoViewController named closeInfoPressed occurs, it calls infoViewController's method closeInfoMethod like self closeInfoMethod:introVC.
In that method I remove self.view from Superview and set introVC.view's Alpha to 1 like introVC.view setAlpha:1.0f
So the codesnippets are
intro.h
IBOutlet InfoscreenViewController *infoScreenVC;
#property (nonatomic, retain) IBOutlet InfoscreenViewController *infoScreenVC;
- (IBAction) openInf:(id)sender;
intro.m
#synthesize infoScreenVC;
- (IBAction) openInf:(id)sender {
UIViewController *introVC = self;
[infoScreenVC openInfoMethod:introVC];
}
info.h:
- (void) openInfoMethod:(UIViewController *)rootVC;
- (void) closeInfoMethod:(UIViewController *)callingVC;
info.m
- (void) closeInfoMethod:(UIViewController *)callingVC;{
[self.view removeFromSuperview];
[callingVC.view setAlpha:1.0f];
}
- (IBAction) closeInfoPressed{
[self closeInfoMethod:introVC];
[self.view removeFromSuperview];
}
If your problem is "figure out how to tell my app who was the calling VC to get back there", why don't you add a parameter to openInfo selector, like here:
info.h
- (void) openInfoMethod:(introViewController *)introVC;
info.m
- (void) openInfoMethod:(introViewController *)introVC {
<your implementation here>
}
would this work for you?
EDIT: your code from intro.m has got a small problem,
- (IBAction) openInf:(id)sender {
introViewController *introVC;
[infoScreenVC openInfoMethod:];
}
indeed, you are not initializing your introVC variable, so that when you pass it into -openInfoMethod: it will have some weird value and cause a crash.
As far as I can grasp from your code, intro.m should be the implementation file for your introViewController, therefore you can simply call:
[infoScreenVC openInfoMethod:self];
but please, before doing this, confirm that self is actually your introViewController.
This should be ilke this
(void) openInfoMethod:(introViewController *)introvc you are passing parameters in a wrong way.
If I understand your question correctly, your infoView should be a subclass of UIView and instantiated from any of your view controllers using:
InfoView *infoView = [[InfoView alloc] initWithFrame:CGRectMake(originX,originY,width,height)];
Then you simply add it as a subview of your view controllers view:
[self.view addSubView:infoView];
[infoView release]; // It's safe to release it here as your view controller's view is retaining it
And when you are done with it, simply call
[infoView removeFromSuperview];
As an aside, you could create some simple methods inside infoView that include introducing animation when the view is presented or removed. Here's an example for fade in and out which assume you set the alpha to zero initially when you create the view:
- (void)fadeIn {
[UIView animateWithDuration:1.0f animations:^{self.alpha = 1.0f}];
}
And fade out
- (void)fadeOut {
[UIView animateWithDuration:1.0f animations:^{self.alpha = 0f}
completion:^(BOOL finished){self removeFromSuperView}];
}

viewDidLoad method not being called using presentModalDialog method

I am using presentModalDialog to show a viewcontroller's view however the viewDidLoad never gets called?!
Is there any method that is called where I can place my logic that fills and configures the view?
EDIT:
It's a bit difficult to put a small amount of code, you kind of need to see it all, but here goes:
I have 2 nibs and 2 view controllers (portrait-mainvc/landscape) which both inherit from 1 baseclass which has the logic and the iboutlets. This is to allow me to re-use code. When the orientation changes in the main controller, it switches between the 2 controllers (modal dialog) which in turn use their respective nib's however they all use the same base code to configure the UI items.
#implementation HomeViewControllerBase
- (void)configureBestSellItems
{
[self startRetrievingRegions];
// load all the images from our bundle and add them to the scroll view
// NSUInteger i;
for (int i = 0; i <= 150; i++)
{
NSString *imageName = #"tempImage.jpg";
UIImage *image = [UIImage imageNamed:imageName];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
// setup each frame to a default height and width, it will be properly placed when we call "updateScrollList"
CGRect rect = imageView.frame;
rect.size.height = kScrollObjHeight;
rect.size.width = kScrollObjWidth;
imageView.frame = rect;
imageView.tag = i; // tag our images for later use when we place them in serial fashion
[self.bestSellScrollView addSubview:imageView];
[imageView release];
}
[self layoutScrollImages]; // now place the photos in serial layout within the scrollview
}
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad
{
[super viewDidLoad];
[self configureBestSellItems];
}
#end
#interface HomeViewController : HomeViewControllerBase
{
}
#interface HomeViewController_Landscape : HomeViewControllerBase
{
}
#implementation HomeViewController
//This works for portrait
- (void)viewDidLoad
{
[super viewDidLoad];
}
#end
#implementation HomeViewController_Landscape
//This does not work
- (void)viewDidLoad
{
[super viewDidLoad];
}
//This works as suggested
- (void)awakeFromNib
{
[super configureBestSellItems];
}
#end
You can try these in your viewcontroller:
-(void)awakeFromNib
{
[super awakeFromNib];
// your code here
}
or
-(void) viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
// your code here
}
However, you should try to find why viewDidLoad is never called. You can also check this
link.
You may have a custom function in there named presentModalDialog:, as searching apple's docs doesn't have that function. You're calling a custom function that loads a Xib.
I'm doing the same thing.
You want this:
[self presentModalViewController:controller...
ViewDidLoad is called by Controllers, if the awakeFromNib is being called, you're probably getting, by itself, the view.
It's a common problem in objective c:
you should review the flowing:
1-initiation of the view controller you should initialize it in the right way in app delegate.
2-review the file owner of the view it must be the right class which usually the same name of uiview.

UIView background color not set until device rotates

I'm working with a simple iPad application, and I've got a simple problem. I'm setting up a custom UIView; here's my initWithFrame:
- (id)initWithFrame:(CGRect)frame
{
if ((self = [super initWithFrame:frame]))
{
self.backgroundColor = [UIColor colorWithWhite:.4 alpha:1.0];
....
}
return self;
}
The problem is that when the app starts, in this view the background color is not applied, even though the rest of the init is running (controls are added, etc). When I rotate the device though, the background color is applied, and will remain applied for the lifetime of the app. I think I'm missing a layout command somewhere, but I'm not sure where. Any ideas?
EDIT 2
Here's the call to init the view. These methods are in my ViewController.
-(id)initWithFrame:(CGRect)_rect
{
rect = _rect;
if(self = [super init])
{
......
}
return self;
}
- (void)loadView
{
[super loadView];
myView = [[MyView alloc]
initWithFrame:rect];
self.view = myView;
}
The -initWithFrame: is called by Interface Builder, but not by the NSCoder instance that unarchives your view from the nib file at runtime. Instead, the unarchiving mechanism calls -initWithCoder:, so your view subclass would need to do something like this:
- (id)initWithCoder:(NSCoder *)decoder
{
if ((self = [super initWithCoder:decoder]))
{
self.backgroundColor = [UIColor purpleColor];
// Do other initialization here...
}
return self;
}
Try to change the background in the viewDidLoad method?
Mmmm
If you don't have the call method (wich alloc your UIView) we won't be able to help you.
It can be because your uiview frame isn't set up correctly.
Or because you don't call initWithFrame after alloc but you cal init function.
So give us more information if you want be helped :-)
Good luck !