Adding a CALayer sublayer inside of UIView init - ios

I'm trying to add a CALayer as a sublayer in a UIView subclass, but when I add the sublayer inside the init method I get EXC_BAD_ACCESS when I add the view to another view or window.
Init method:
- (id)initWithTitle:(NSString *)title message:(NSString *)message
{
if ((self = [super init]))
{
self.title = title;
self.message = message;
self.alertLayer = [[CALayer alloc] init];
self.layer.cornerRadius = kCORNER_RADIUS;
self.layer.shadowRadius = 3.0;
self.layer.shadowColor = [UIColor blackColor].CGColor;
self.layer.shadowOffset = CGSizeMake(15, 20);
self.layer.shadowOpacity = 1.0;
self.alertLayer.delegate = self;
self.alertLayer.masksToBounds = YES;
self.alertLayer.cornerRadius = kCORNER_RADIUS;
[self.layer addSublayer:self.alertLayer]; // This line of code seems to cause EXC_BAD_ACCESS
}
return self;
}
EXC_BAD_ACCESS is caused after calling [self.view addSubview:alertView] inside a view controller or UIWindow.

You have two layers (self.layer and self.alertLayer) which have the same delegate self, this leads to an infinite recursion in internal method -[UIView(Hierarchy) _makeSubtreePerformSelector:withObject:withObject:copySublayers:] when added this view (self) to the view tree. Therefore you must remove self.alertLayer.delegate = self; to avoid crash. If you need to delegate for alarmLayer you can create distinct object.

Related

Custom UIView getting display late

I'm facing an weird issue, I'm developing a iOS static library, this static library have some custom UIViews. These custom views contains UIButtons and IBOutlets for those buttons, Custom UIView has been create in Xib. Now this static library I'm including inside another static library and I'm attaching custom UIView
of library one in to UIViewController's UIView of static library two with -
[self.view addSubview:aCustomViewFromLibrary1];
So when I'm using Library 2 in to some product everything goes great but when code of adding custom UIView of static library one to static library two runs everything goes right but custom UIView appears late, about half a minutes late.
here is the code for one of the custom UIView in static library one...
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(!self){
return nil;
}
_otpTextField.delegate = self;
loadView()
_approveOtpBtn.layer.cornerRadius = 5;
NSLog(#"%#",[NSString stringWithFormat:#"%s", __PRETTY_FUNCTION__]);
viewFrame = frame;
CALayer *layer = self.layer;
layer.shadowOffset = CGSizeMake(1, 1);
layer.shadowColor = [[UIColor blackColor] CGColor];
layer.shadowRadius = 4.0f;
layer.shadowOpacity = 0.80f;
layer.shadowPath = [[UIBezierPath bezierPathWithRect:layer.bounds] CGPath];
return self;
}
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if(!self){
return nil;
}
loadView()
_approveOtpBtn.layer.cornerRadius = 5;
NSLog(#"%#",[NSString stringWithFormat:#"%s", __PRETTY_FUNCTION__]);
return self;
}
-(void) awakeFromNib{
NSLog(#"%#",[NSString stringWithFormat:#"%s", __PRETTY_FUNCTION__]);
_approveOtpBtn.layer.cornerRadius = 5;
}
// Approve OTP
-(IBAction)approveOtp:(UIButton *) aButton{
NSLog(#"%#",[NSString stringWithFormat:#"%s", __PRETTY_FUNCTION__]);
}
// minimize custome browser
-(IBAction)minimizeCB:(UIButton *) aButton{
NSLog(#"%#",[NSString stringWithFormat:#"%s", __PRETTY_FUNCTION__]);
}
- (void) RegenerateOTP:(UIButton *) aButton{
NSLog(#"%#",[NSString stringWithFormat:#"%s", __PRETTY_FUNCTION__]);
}
------UPDATE------
This is how I'm adding above custom View in the UIViewController's UIView.
- (void) updateView{
if([paymentOption isEqualToString:CHOOSE]){
if(_choose){
[_choose removeFromSuperview];
_choose = nil;
}
if(_approveOTP){
[_approveOTP removeFromSuperview];
_approveOTP = nil;
}
if(_regenOTPView){
[_regenOTPView removeFromSuperview];
_regenOTPView = nil;
}
_choose = [[CBAllPaymentOption alloc] initWithFrame:CGRectMake(0,self.resultView.frame.size.height - 227,self.resultView.frame.size.width,227)];
_choose.bankJS = _bankSpecificJavaScriptDict;
_choose.handler = self;
NSLog(#"loadJavascript AllOptionView view = %# ResultView = %#",_choose,_resultView);
//[_resultView addSubview:_choose];
if(_connectionHandlerDelegate && [_connectionHandlerDelegate respondsToSelector:#selector(addViewInResultView:)]){
[_connectionHandlerDelegate addViewInResultView:_choose];
}
[_resultView bringSubviewToFront:_choose];
// view getting display late so call setNeedDisplay.
[_choose setNeedsDisplay];
_choose.isViewOnScreen = YES;
}
}
Any solution and suggestion are welcome
Any changes you make to the UI must be done on the main thread.
You can try something like this.
dispatch_async(dispatch_get_main_queue(), ^{
// perform updates here
});

Sublayer only draws when created in initWithFrame, not initWithCoder

I have a custom view with a sublayer (CAShapeLayer) and subview (UILabel). When I create the layer in initWithCoder and set the background color, it always shows up as black. However, if I move the code into initWithFrame, the color shows up successfully.
Are we not supposed to create sublayers in initWithCoder?
Here is the only way I can get my code to work:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.colorLayer = [CAShapeLayer layer];
self.colorLayer.opacity = 1.0;
[self.layer addSublayer:self.colorLayer];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
self.textLabel = [[UILabel alloc] initWithFrame:self.bounds];
self.textLabel.font = [UIFont primaryBoldFontWithSize:12];
self.textLabel.textColor = [UIColor whiteColor];
self.textLabel.textAlignment = NSTextAlignmentCenter;
self.textLabel.backgroundColor = [UIColor clearColor];
[self addSubview:self.textLabel];
}
return self;
}
- (void)drawRect:(CGRect)rect {
//Custom drawing of sublayer
}
UPDATE:
Turns out in my drawRect I was setting the fill color wrong. I should have used colorLayer.fillColor = myColor.CGColor instead of [myColor setFill] then [path fill]
The difference between initWithFrame: and initWithCoder: is that initWithCoder: is called when the view is created from storyboard/nib.
If you add it programatically, for example:
UIView *v = [[UIView alloc] initWithFrame:...];
[self.view addSubview:v];
initWithFrame: is called.
The good idea is to create base init method and call it in both init. In that way the initialisation sets up all of the properties in both scenario, when the view is added programatically or in storyboard.
For example:
-(void)baseInit {
self.colorLayer = [CAShapeLayer layer];
self.colorLayer.opacity = 1.0;
//... other initialisation
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self baseInit];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self baseInit];
}
return self;
}

CALayer, subLayer alpha overriding

I have a CALayer ( superlayer ) with 0.3 opacity property. The superlayer contains another CALayer(sublayer). Although sublayer does not have a opacity property superLayer's opacity affects sublayer's appearance. Is there a method that I can override superLayer'a opacity property.
SuperLayer
#implementation SuperView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setOpaque:NO];
[self.layer setOpaque:NO];
}
return self;
}
-(void)drawRect:(CGRect)rect
{
CALayer* superLaer=self.layer;
superLaer.backgroundColor = [UIColor blackColor].CGColor;
superLaer.shadowOffset = CGSizeMake(0, 3);
superLaer.shadowRadius = 5.0;
superLaer.shadowColor = [UIColor blackColor].CGColor;
superLaer.shadowOpacity = 0.6;
superLaer.frame = CGRectMake(10, 10, 300,300);
superLaer.borderColor = [UIColor blackColor].CGColor;
superLaer.borderWidth = 2.0;
superLaer.cornerRadius = 10.0;
superLaer.cornerRadius = 10.0;
superLaer.masksToBounds=YES;
superLaer.opacity = 0.1;
}
#end
SubLayer
#implementation SubView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setOpaque:NO];
[self.layer setOpaque:NO];
}
return self;
}
-(void)drawRect:(CGRect)rect
{
CALayer* superLaer=self.layer;
superLaer.backgroundColor = [UIColor redColor].CGColor;
superLaer.shadowOffset = CGSizeMake(0, 3);
superLaer.shadowRadius = 5.0;
superLaer.shadowColor = [UIColor blackColor].CGColor;
superLaer.shadowOpacity = 0.6;
superLaer.frame = CGRectMake(100, 100, 100,100);
superLaer.borderColor = [UIColor blackColor].CGColor;
superLaer.borderWidth = 2.0;
superLaer.cornerRadius = 10.0;
superLaer.cornerRadius = 10.0;
superLaer.masksToBounds=YES;
}
#end
I have added the subView to the superView in Viewcontroller.
Based on your question, The super layer's opacity controls the sublayers' opacity,
you could subclass CALayer and override some of its properties in init method like
#implementation SomeSuperCALayer
-(id)init
{
self = [super init];
if (self) {
[self setup];
}
return self;
}
#define W_H 100.0
#define X_Y 50.0f
-(void)setup
{
self.frame = CGRectMake(X_Y, X_Y, W_H, W_H);
self.backgroundColor = [UIColor blueColor].CGColor;
self.opacity = 1.0f; // big opacity
}
#implementation SomeSubCALayer
-(id)init
{
self = [super init];
if (self) {
[self setup];
}
return self;
}
#define W_H 100.0
#define X_Y 150.0f
-(void)setup
{
self.frame = CGRectMake(X_Y, X_Y, W_H, W_H);
self.backgroundColor = [UIColor blueColor].CGColor;
self.opacity = .5f; // small opacity
}
# implementation MyViewController
//
-(void)viewDidLoad
{
SomeSuperCALayer *superLayer = [[SomeSuperCALayer alloc] init]; SomeSubCALayer *subLayer = [[SomeSubCALayer alloc] init];
//add sub layer to super layer
[superLayer addSubLayer:subLayer];
// add super layer to main View
[self.view.layer addSubLayer:superLayer];
//
//...
}
Also don't use drawRect for calling layer's properties, drawRect is mean't for drawing like the Apple's documentation dictates:
/ Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.

Is this the way to create a custom UIView?

I've subclassed UIView so I can add multiple view of the same type to my project. The view is basically a rectangle with a radius, a picture in the left side and a piece of text on the right. Now don't get me wrong the code bellow works and all but i'm always asking myself "is this the way, or am I getting it really wrong".
1) I'm mixing CALayers with UILabels, is this ok?
2) Is the setUpView the way its done.
3) If this is ok, whats my best bet for letting the imageLayer respond to touch events?
4) Could I just ask a side question as well. As a part time beginner with only one basic app in the store am I been a bit picky. What I mean is, should I just make it work and crack on! What do you think?
#synthesize dateLabel = _dateLabel;
#synthesize nameLabel = _nameLabel;
#synthesize photo = _photo;
#synthesize roundRect= _roundRect;
#synthesize imageLayer = _imageLayer;
-(void)setUpView
{
//create the white rectangle
self.roundRect.frame = CGRectMake(0.0, 0.0, self.frame.size.width, self.frame.size.height);
self.roundRect.cornerRadius = 15.0;
self.roundRect.backgroundColor = [UIColor clearColor].CGColor;
self.roundRect.borderColor = [UIColor whiteColor].CGColor;
self.roundRect.borderWidth = 2.0;
self.roundRect.masksToBounds = YES;
//create the photo
CGImageRef image = [self.photo CGImage];
self.imageLayer.frame = CGRectMake(0.0, 0.0, self.roundRect.frame.size.width*0.48, self.roundRect.frame.size.height);
self.imageLayer.borderColor = [UIColor whiteColor].CGColor;
self.imageLayer.borderWidth = 2.0;
self.imageLayer.masksToBounds = YES;
[self.imageLayer setContents:(__bridge id)image];
[self.imageLayer setContentsGravity:kCAGravityResizeAspectFill];
[self.imageLayer setContentsRect:CGRectMake(0.0,0.0,1.1,1.0)];
//create label
self.nameLabel.frame = CGRectMake(self.imageLayer.frame.size.width+5, self.roundRect.frame.size.height*0.05, self.roundRect.frame.size.width*0.48,self.roundRect.frame.size.height*0.35);
self.nameLabel.backgroundColor = [UIColor clearColor];
self.nameLabel.textAlignment = UITextAlignmentCenter;
self.nameLabel.baselineAdjustment = UIBaselineAdjustmentAlignCenters;
self.nameLabel.textColor = [UIColor whiteColor];
self.nameLabel.adjustsFontSizeToFitWidth =YES;
self.nameLabel.font = [UIFont fontWithName:#"Gill Sans" size:50.0];
[self.roundRect addSublayer:self.imageLayer];
[self.layer addSublayer:self.roundRect];
[self addSubview:self.nameLabel];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
}
-(void)setPhoto:(UIImage *)photo
{
_photo = photo;
[self setUpView];
}
-(void)setNameLabel:(UILabel *)nameLabel
{
_nameLabel = nameLabel;
[self setUpView];
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.roundRect = [[CALayer alloc]init];
self.imageLayer = [[CALayer alloc]init];
self.nameLabel = [[UILabel alloc]init];
[self setUpView];
}
return self;
}
You could make this easier by using the Interface Builder. I would create a XIB with a UIView set to the size I want. Then add a label (nameLabel) and configure it how you like. You can use QuartzCore layers cornerRadius to set your rounded corners. Then you can use your subclass in the XIB and set it as the class type for the UIView. Not the file owner but the view. Then you can link your label as an outlet and you won't need to create it manually. You can remove some of the code above and still have your functionality and looks. I've learned to let the Interface Builder do the work when you can.
Hope this helps.

Custom UIPopoverBackgroundView: no drop shadow

I've used this good tutorial to create a custom UIPopoverBackgroundView class.
It works well. The only problem is that I am not getting the typical UIPopoverController drop shadow and I want it. I've tried specifying it on the layer of my UIPopoverBackgroundView instance without success. My instance of UIPopoverController doesn't seem to have a public view to manipulate. Adding it to the popover content also doesn't work.
Probably really simple: how do I add a drop shadow when using a custom UIPopoverBackgroundView class?
// UIPopoverBackgroundView.m
-(id)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
_borderImageView = [[UIImageView alloc] initWithImage:[[UIImage imageNamed:#"bg-popover.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(CAP_INSET,CAP_INSET,CAP_INSET,CAP_INSET)]];
_arrowView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"bg-popover-arrow.png"]];
[self addSubview:_borderImageView];
[self addSubview:_arrowView];
self.layer.shadowOffset = CGSizeMake(50, 50);
self.layer.shadowColor = [[UIColor blackColor] CGColor];
}
return self;
}
You don't need to add your own shadows. The base UIPopoverBackgroundView will do it for you. Just make sure to call super in your layoutSubviews implementation.
EDIT: My comment applies to apps targeting iOS 6.
Ok, figured it out. I needed to add the drop shadow to the borderImageView, not the view of the popover instance.
- (id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
_borderImageView = [[UIImageView alloc] initWithImage:[[UIImage imageNamed:#"bg-popover.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(CAP_INSET,CAP_INSET,CAP_INSET,CAP_INSET)]];
_arrowView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"bg-popover-arrow.png"]];
[self addSubview:_borderImageView];
[self addSubview:_arrowView];
_borderImageView.layer.cornerRadius = 5.0f;
_borderImageView.layer.masksToBounds = NO;
_borderImageView.layer.borderWidth = 1.0f;
_borderImageView.layer.borderColor = [UIColor blackColor].CGColor;
_borderImageView.layer.shadowColor = [UIColor blackColor].CGColor;
_borderImageView.layer.shadowOpacity = 0.8;
_borderImageView.layer.shadowRadius = 50;
_borderImageView.layer.shadowOffset = CGSizeMake(-10.0f, 10.0f);
}
return self;
}

Resources