I have subclassed an UINavigationController adding a drop shadow on method viewDidApper: from UINavigationController like this:
- (void)viewDidAppear:(BOOL)animated
{
// Add shadow to Navigation Bar
CAGradientLayer *newShadow = [[CAGradientLayer alloc] init];
newShadow.shadowColor = [[UIColor blackColor] CGColor];
newShadow.shadowOffset = CGSizeMake(0.0f, 4.0f);
newShadow.shadowOpacity = 1.0f;
CGRect shadowPath = CGRectMake(self.navigationBar.layer.bounds.origin.x - 10, self.navigationBar.layer.bounds.size.height - 6, self.navigationBar.layer.bounds.size.width + 20, 5);
newShadow.shadowPath = [UIBezierPath bezierPathWithRect:shadowPath].CGPath;
newShadow.shouldRasterize = YES;
self.navigationBar.clipsToBounds = NO;
[self.navigationBar.layer addSublayer:newShadow];
[super viewDidAppear:animated];
}
And when I use this NavigationController in a ModalViewController, the shadow repaints over and over.
Because the - viewDidAppear: method is called every time the view appears. You may want to move this code into the - viewDidLoad method.
Related
I have a custom UITableView subclass that inserts a separator line as a subview, like this:
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
CGRect frame = [self bounds];
frame.origin.y = CGRectGetMaxY(frame) - 1;
frame.size.height = 1;
UIView *separator = [[[UIView alloc] initWithFrame:frame] autorelease];
[separator setBackgroundColor:[UIColor whiteColor]];
[separator setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin];
[self addSubview:separator];
}
return self;
}
This draws a nice line beneath my cell. However, when the tableview is in edit mode and I drag to reorder the cells, this subview disappears!
Everything else looks the same, and the opacity of the cell is reduced as it is dragged, but why would this subview be removed?
Overriding drawRect works:
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextSetFillColorWithColor(context, [[UIColor whiteColor] CGColor]);
CGContextFillRect(context, CGRectMake(0, CGRectGetMaxY(rect)-.5, CGRectGetWidth(rect), .5));
CGContextFillRect(context, CGRectMake(0, 0, CGRectGetWidth(rect), .5));
CGContextRestoreGState(context);
}
Instead of adding a separator line as a subview, use the default one that table view comes with and change the borderColor via the cell's backgroundView layer.
cell.backgroundView.layer.borderColor = [UIColor blueColor];
#import <QuartzCore/QuartzCore.h> is needed, obviously.
Also, if you're using a grouped table view, make sure to match the outside border:
tableView.separatorColor = [UIColor blueColor];
Make sure to the separator line again, of course :) (So don't set the tableView separatorStyle property to none: tableView.separatorStyle = UITableViewCellSeparatorStyleNone)
I want to draw an rectangle and can change the rectangle's position and size realtime.
I have tried following two methods, but the view don't show the rectangle.
do you have any advice or example?
appreciate your help.
use view frame (it only show the new rectangle when restart the app)
UIView * drawView = [[UIView alloc] initWithFrame:CGRectMake(10, 10, 10, 10)];
[self.view addSubview:drawView];
drawView.layer.borderColor = [[UIColor orangeColor] CGColor];
drawView.layer.borderWidth = 2;
drawView.frame = CGRectMake(r.x, r.y, r.width, r.height);
draw in the drawRect
fav = [[FaceAugmentingView alloc] initWithFrame:[imageView frame]];
fav.backgroundColor = [UIColor clearColor];
[self.view addSubview: fav ];
fav.face = CGRectMake(r.x, r.y, r.width, r.height);
[fav setNeedsDisplay];
I suggest you add a method that reloads the faces based whenever you need to alter it. Declare and array that will help us keep track of all the views we've added.
#interface FacesViewController: UIViewController
[..]
#property (nonatomic, retain) NSMutableArray * faceViews;
- (void)reloadFaces;
[..]
#end
And then implement reloadFaces something like this -
- (void)reloadFaces {
/* remove all current views we've added */
for ( UIView * aView in self.faceViews ) {
[aView removeFromSuperview];
}
[self.faceViews removeAllObjects];
/* Add news ones */
int numberOfFaces = [self numberOfFacesInImage];
for ( int i = 0; i < numberOfFaces; i++ ) {
CGRect faceFrame = [self frameForFaceAtIndex:i];
UIView * drawView = [[[UIView alloc] initWithFrame:faceFrame] autorelease];
drawView.layer.borderColor = [[UIColor orangeColor] CGColor];
drawView.layer.borderWidth = 2;
drawView.frame = CGRectMake(r.x, r.y, r.width, r.height);
[self.view addSubview:drawView];
[faceViews addObject:drawView];
}
}
I would expect reloadFaces to be called when there is a change of some kind. numberOfFacesInImage and frameForFaceAtIndex: are methods that you will have to implement based on how you get the data. You can also replace them to suit your data model. Don't forget to initialize faceViews.
I am subclassing UIScrollView and adding some visual elements. At the top, there should be a gradient going from black to clear, and at the bottom there is a mask that fades out towards the bottom. I have those layers added and looking right, but when I scroll, they stay at the coordinates I put them in (with respect to the scroll view), rather than being "fixed" to the bottom and top of the view. This scroll view only scrolls vertically.
Here is the code for SettingsScrollView.m:
#import "SettingsScrollView.h"
#import <QuartzCore/QuartzCore.h>
#define SHADOW_HEIGHT 20.0
#define SHADOW_INVERSE_HEIGHT 10.0
#define SHADOW_RATIO (SHADOW_INVERSE_HEIGHT / SHADOW_HEIGHT)
#implementation SettingsScrollView
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
[self setUpShadow];
return self;
}
- (CAGradientLayer *)shadowAsInverse:(BOOL)inverse
{
CAGradientLayer *newShadow = [[[CAGradientLayer alloc] init] autorelease];
CGRect newShadowFrame = CGRectMake(0, 0, self.frame.size.width, inverse ? SHADOW_INVERSE_HEIGHT : SHADOW_HEIGHT);
newShadow.frame = newShadowFrame;
CGColorRef darkColor =[UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:inverse ? (SHADOW_INVERSE_HEIGHT / SHADOW_HEIGHT) * 0.5 : 0.5].CGColor;
CGColorRef lightColor = [self.backgroundColor colorWithAlphaComponent:0.0].CGColor;
newShadow.colors = [NSArray arrayWithObjects: (id)(inverse ? lightColor : darkColor), (id)(inverse ? darkColor : lightColor), nil];
return newShadow;
}
- (CAGradientLayer *)gradientMask
{
CAGradientLayer *mask = [[[CAGradientLayer alloc] init] autorelease];
CGRect maskFrame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);
mask.frame = maskFrame;
CGColorRef darkColor =[UIColor clearColor].CGColor;
CGColorRef lightColor =[UIColor blackColor].CGColor;
mask.colors = [NSArray arrayWithObjects: (id)lightColor, (id)lightColor, (id)darkColor, nil];
mask.locations = [NSArray arrayWithObjects:[NSNumber numberWithFloat:0.0], [NSNumber numberWithFloat:0.9], [NSNumber numberWithFloat:1.0], nil];
return mask;
}
- (void)setUpShadow
{
CAGradientLayer *topShadowLayer = [self shadowAsInverse:NO];
CAGradientLayer *bottomShadowLayer = [self gradientMask];
[self.layer insertSublayer:topShadowLayer atIndex:0];
[self.layer setMask:bottomShadowLayer];
[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
CGRect topShadowLayerFrame = topShadowLayer.frame;
topShadowLayerFrame.size.width = self.frame.size.width;
topShadowLayerFrame.origin.y = 0;
topShadowLayer.frame = topShadowLayerFrame;
CGRect bottomShadowLayerFrame = bottomShadowLayer.frame;
bottomShadowLayerFrame.size.width = self.frame.size.width;
bottomShadowLayerFrame.origin.y = self.frame.size.height - bottomShadowLayer.frame.size.height;
bottomShadowLayer.frame = bottomShadowLayerFrame;
[CATransaction commit];
}
#end
I know one solution for the top could just be to add a separate view that contains a gradient, but for the bottom I believe I need to use a mask to have it do what I want it to (fade out into the background at the very bottom). The background is an image so I can't just fade to white or another color, it needs to fade to clear. I've been looking for a method that gets called when the scroll view is moved and use that to change the position of the mask, but I haven't found anything yet. Any suggestions?
You could probably accomplish this by overriding -layoutSubviews on the UIScrollView, which is invoked when its bounds change.
Another technique I've seen on views like this is that instead of doing this layer management from the view, subclass CALayer, use your subclass as the scroll view's layer, and then in your layer's -layoutSublayers, do to the same work when it moves around. I mention this latter technique, because it seems somewhat more natural for the layer to be managing its sublayers vs the view managing its layers sublayers directly.
I want to add shadow effect for UINavigationbar like GameCenter.
I think apply background image with shadow to nav bar, but title's line height would be down.
And I draw shadow to background, but background image would not scroll.
What is the best Practice of this case??
You can subclass UINavigationController and then have a shadow layer for each navigation or if your bar is always visible just add the shadow to UIWindow (only one for the entire application) and then make it the frontmost view each time you add a subview.
CGColorRef darkColor = [[UIColor blackColor] colorWithAlphaComponent:.5f].CGColor;
CGColorRef lightColor = [UIColor clearColor].CGColor;
CAGradientLayer *newShadow = [[[CAGradientLayer alloc] init] autorelease];
newShadow.frame = CGRectMake(0, self.navigationBar.frame.size.height, self.navigationBar.frame.size.width, 10);
newShadow.colors = [NSArray arrayWithObjects:(id)darkColor, (id)lightColor, nil];
[self.navigationBar.layer addSublayer:newShadow];
If you choose the latter case then override the didAddSubview to make the layer the frontmost:
CALayer *superlayer = self.shadowLayer.superlayer;
[self.shadowLayer removeFromSuperlayer];
[superlayer addSublayer:self.shadowLayer];
Hope it helps.
It's easly done with custom subclass of UINavigationController. The key is to overwrite -viewWillAppear: and add sublayer to UINavigationController's view's layer:
- (void)viewWillAppear:(BOOL)animated
{
CGColorRef darkColor = [[UIColor blackColor] colorWithAlphaComponent:.5f].CGColor;
CGColorRef lightColor = [UIColor clearColor].CGColor;
CGFloat navigationBarBottom;
navigationBarBottom = self.navigationBar.frame.origin.y + self.navigationBar.frame.size.height;
CAGradientLayer *newShadow = [[[CAGradientLayer alloc] init] autorelease];
newShadow.frame = CGRectMake(0,navigationBarBottom, self.view.frame.size.width, 10);
newShadow.colors = [NSArray arrayWithObjects:(id)darkColor, (id)lightColor, nil];
[self.view.layer addSublayer:newShadow];
[super viewWillAppear:animated];
}
navigationBarBottom accounts for both UINavigationBar and status bar.
Credit for gradient layer parameters goes to marcio.
Drawing a CAGradientLayer gives a very uniform, but - in my opinion - a rather unnatural looking shadow.
Here's an alternative approach based upon the answer to question UIView with shadow.
It uses the various shadow properties of CALayer to give the shadow effect:
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationController.navigationBar.layer.shadowColor = [[UIColor blackColor] CGColor];
self.navigationController.navigationBar.layer.shadowOffset = CGSizeMake(0.0f,0.0f);
self.navigationController.navigationBar.layer.shadowOpacity = 1.0f;
self.navigationController.navigationBar.layer.shadowRadius = 4.0f;
}
The same technique works with the tabBarController's tabBar;
self.tabBarController.tabBar.layer.shadowColor = [[UIColor blackColor] CGColor];
self.tabBarController.tabBar.layer.shadowOffset = CGSizeMake(0.0f,0.0f);
self.tabBarController.tabBar.layer.shadowOpacity = 1.0f;
self.tabBarController.tabBar.layer.shadowRadius = 4.0f;
I'm programatically adding two custom UIPickerViews to another view (MainView). They work just fine but they are not visible until a touch event occurs in any part of the MainView. I've checked the class references for UIPickerView and UIView but haven't found anything that manages to "refresh" the view, unless I'm missing something obvious?
Here's my drawRect method in MainView.m. I've tried doing the same thing in viewDidLoad but with no success. Could the custom rotations/transforms etc have something to do with it?
- (void)drawRect:(CGRect)rect {
CGRect pickerFrame = CGRectMake(50, -32, 30, 180);
m_picker1 = [[UIPickerView alloc] initWithFrame:pickerFrame];
m_picker1.delegate = self;
m_picker1.tag = k_ptag1;
m_picker1.showsSelectionIndicator =YES;
m_picker1.backgroundColor = [UIColor clearColor];
CGAffineTransform rotate = CGAffineTransformMakeRotation(3.14/2);
rotate = CGAffineTransformScale(rotate, 0.075, 0.85);
[m_picker1 setTransform:rotate];
[self addSubview:m_picker1];
pickerFrame = CGRectMake(50, 67, 30, 180);
m_picker2 = [[UIPickerView alloc] initWithFrame:pickerFrame];
m_picker2.delegate = self;
m_picker2.tag = k_ptag2;
m_picker2.showsSelectionIndicator =YES;
m_picker2.backgroundColor = [UIColor clearColor];
rotate = CGAffineTransformMakeRotation(3.14/2);
rotate = CGAffineTransformScale(rotate, 0.075, 0.85);
[m_picker2 setTransform:rotate];
[self addSubview:m_picker2];
}
You add subviews in a view's controller, not the view itself. I would suggest familiarising yourself with the MVC design pattern.
drawRect is only supposed to be used for the actual drawing of the view itself, not subviews.