Gradient layer on tableview cell prevention selection colour from appearing - objective-c

I tried to spruce my application up a bit by adding a CGGradientLayer to my tableview cells, and the code works great.
Only problem is that, now, whenever I select a tableview cell, it does not change colour to the selection style, whether blue or grey.
The code I am using for my gradient is
UIView *view = [[UIView alloc] initWithFrame:cell.frame];
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = view.bounds;
gradient.colors = [NSArray arrayWithObjects:(id)[[UIColor colorWithRed:0.3 green:0.3 blue:0.3 alpha:1.0] CGColor], (id)[[UIColor colorWithRed:0.28 green:0.28 blue:0.28 alpha:1.0] CGColor], nil];
[cell.layer insertSublayer:gradient atIndex:0];
I do know that my cell is being selected as the text in my cells changes colour to white, which is the default selection colour.
So what I am trying to fix is the selection colour on my cells. I don't mind if I can't use the selection colours provided, but in that case can I maybe add my own transparent rect over the cell on selection?

When a UITableViewCell is selected, it inserts the selection background at index 0. That ends up being under your gradient. The view hierarchy looks like this:
<MyTableViewCell: 0xd26fb80; baseClass = UITableViewCell; frame = (0 44; 320 44); autoresize = W; layer = <CALayer: 0xd26fc90>>
| <UITableViewCellSelectedBackground: 0x6880810; frame = (0 0; 320 43); layer = <CALayer: 0x68855c0>>
| <CAGradientLayer: 0xd26ecb0> (layer)
| <UITableViewCellContentView: 0xd26fcc0; frame = (0 0; 320 43); opaque = NO; layer = <CALayer: 0xd26fd00>>
| <UIView: 0xd26e8e0; frame = (0 43; 320 1); autoresize = W+TM; layer = <CALayer: 0xd26f1a0>>
Notice that the CAGradientLayer, which I inserted at index zero when the cell was created, is after UITableViewCellSelectedBackground, so the gradient layer overlays the selected background layer visually.
To fix it, make your own subclass of UITableViewCell. Make the gradient layer a property or ivar of your subclass:
#implementation MyTableViewCell
{
CAGradientLayer *_gradient;
}
- (void)initGradient
{
_gradient = [CAGradientLayer layer];
_gradient.opaque = YES;
_gradient.frame = self.bounds;
_gradient.colors = [NSArray arrayWithObjects:(__bridge id)[UIColor redColor].CGColor, (__bridge id)[UIColor blueColor].CGColor, nil];
[self.layer insertSublayer:_gradient atIndex:0];
}
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
if ((self = [super initWithStyle:style reuseIdentifier:reuseIdentifier])) {
[self initGradient];
}
return self;
}
- (void)awakeFromNib
{
[self initGradient];
}
Override setSelected:animated: in your subclass to show or hide the gradient layer based on whether the cell is selected:
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
_gradient.hidden = selected;
}

I came upon this same problem, and since - (void)setSelected:animated: was not always called (such as when the user holds down on the cell, etc.) I found this method to work much better.
Essentially, just put your gradient in the cell.backgroundView.
UIView *cellBackgroundView = [[UIView alloc] initWithFrame:cell.bounds];
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = cellBackgroundView.bounds;
gradient.colors = [NSArray arrayWithObjects:(id)[[UIColor colorWithRed:0.3 green:0.3 blue:0.3 alpha:1.0] CGColor], (id)[[UIColor colorWithRed:0.28 green:0.28 blue:0.28 alpha:1.0] CGColor], nil];
[cellBackgroundView.layer addSublayer:gradient];
cell.backgroundView = cellBackgroundView;
And then set your selectedBackgroundView to however you want. this way, the cell's backgroundView is sure to be completely hidden, layers and all. This also works with subclassing, replace cell. with self..

Related

sendSubviewToBack on UITableView not working properly in iOS 11

I have an UITableViewController to which I successfully applied in the past a gradient background, by sending the newly added subview to back:
//performed on viewDidLoad
UIView *bgView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 1.5*280, 1.5*SCREEN_HEIGHT)];
bgView.backgroundColor = [UIColor yellowColor];
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = bgView.bounds;
gradient.startPoint = CGPointMake(0, 0);
gradient.endPoint = CGPointMake(1, 1);
UIColor *topColor = UIColorFromRGB(0x229f80);
UIColor *bottomColor = UIColorFromRGB(0x621ad9);
gradient.colors = [NSArray arrayWithObjects:(id)[topColor CGColor], (id)[bottomColor CGColor], nil];
[bgView.layer insertSublayer:gradient atIndex:0];
[self.view addSubview:bgView];
[self.view sendSubviewToBack:bgView];
bgView = nil;
However, this no longer works in iOS 11 and the bgView is actually placed on top of all the cells.
Anyone knows how I can fix this?
Or maybe I was doing it wrong all the time?
If your cells are transparent then you can try self.tableView.backgroundView = bgView;
If you don't need the background view to be scrolling together with the table view, you can use
self.tableView.backgroundView = bgView;
If you need the background view to be scrolling, change layer's zPosition to a negative value to make it work in iOS 11:
[self.view insertSubview:bgView atIndex:0];
bgView.userInteractionEnabled = NO;
bgView.layer.zPosition = -1;
Another way to fix it is to call [self.view sendSubviewToBack:bgView]; in tableView:willDisplayCell:forRowAtIndexPath:
It works for not transparent cells also.
it appears that addSubview(UIView) sendSubview(toBack: UIView) no longer works for UITableViewControllers in iOS11. So I swap to this:
// in ViewDidLoad
// set the backgroundImage
let backgroundImage = UIImageView(frame: self.view.bounds)
backgroundImage.image = UIImage(named: "background.png")
// self.view.addSubview(backgroundImage) // NO LONGER WORKS
// self.view.sendSubview(toBack: backgroundImage) // NO LONGER WORKS
self.tableView.backgroundView = backgroundImage

Objective c 2D gradient

I need to make at 2D gradient. I have the first part of it make a gradient vertical direction from a color to a clear color:
+ (void)addFadeout:(UIView *) view withColor:(UIColor *)color {
UIColor * halfClearColor = [color colorWithAlphaComponent:0];
CALayer *layer = view.layer;
layer.masksToBounds = YES;
CAGradientLayer *shineLayer = [CAGradientLayer layer];
shineLayer.frame = layer.bounds;
shineLayer.colors = #[
(id)color.CGColor,
(id)halfClearColor.CGColor];
shineLayer.locations = #[
[NSNumber numberWithFloat:0.0f],
[NSNumber numberWithFloat:1.0f]];
[view.layer insertSublayer:shineLayer atIndex:0];
}
But I also need to be able to add multiple colors in the horizontal direction where they either fade together and is shown separately(still with the vertical gradient). I know I could just add them besides each other but that is not what I am looking for.. How would you do it?
It might be something like this but I can't seem to connect the parts:
http://cupsofcocoa.com/tag/gradient/
https://developer.apple.com/library/mac/#documentation/graphicsimaging/conceptual/drawingwithquartz2d/dq_shadings/dq_shadings.html
Edit 1:
This is what I got now:
It might be unclear what I wanted. I also want a gradient along the x-axis between multiple colors. So what I got combined with something like this: (to replace the red color)
Solution:
+ (void)addFadeout:(UIView *) view withColors:(NSArray *)colors { //CGColors
UIColor * someColor = [UIColor colorWithCGColor:((__bridge CGColorRef)[colors lastObject])];//only alpha channel used with more than one color
UIColor * halfClearColor = [someColor colorWithAlphaComponent:0];
CALayer *layer = view.layer;
layer.masksToBounds = YES;
CAGradientLayer *transparencyLayer = [CAGradientLayer layer];
transparencyLayer.frame = layer.bounds;
transparencyLayer.colors = #[
(id)someColor.CGColor,
(id)halfClearColor.CGColor];
transparencyLayer.locations = #[
[NSNumber numberWithFloat:0.0f],
[NSNumber numberWithFloat:1.0f]];
if(colors.count > 1){
CAGradientLayer * multiColoredLayer = [self getMultiColoredLayerWithColors:colors inLayer:layer];
multiColoredLayer.mask = transparencyLayer;
[view.layer insertSublayer:multiColoredLayer atIndex:0];
}
else{
[view.layer insertSublayer:transparencyLayer atIndex:0];
}
}
+ (CAGradientLayer *)getMultiColoredLayerWithColors:(NSArray *)colors inLayer:(CALayer *)layer{ //CGColors
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = layer.bounds;
gradientLayer.colors = colors;
gradientLayer.startPoint = CGPointMake(0, 0.5);
gradientLayer.endPoint = CGPointMake(1.0, 0.5);
return gradientLayer;
}
You need to use 2 of 'something' as gradients can only be drawn in a single direction at a time. You could do it with 2 CAGradientLayers, one behind the other (or one as a sublayer of the other). Or, you could do it with 2 CGGradients, both drawn into the same context.
From your comment, you want to alpha mask. The best way to apply the 'top' layer with transparency is by setting it as the mask of the 'bottom' layer:
CAGradientLayer *colorLayer = ...;
CAGradientLayer *transparencyLayer = ...;
colorLayer.mask = transparencyLayer;
In this case, any colour in the transparencyLayer is ignored and only the alpha values are used.

Make area around masked region transparent with CALayers?

I'm trying to crop an image to an irregular shape, but I need to make the area that was removed transparent.
Inside subclass of UIView
CALayer *myLayer = [CALayer layer];
CAShapeLayer *mask = [CAShapeLayer layer];
myLayer.frame = self.bounds;
myLayer.contents = (id)[self.picture CGImage];
mask.path = path;
myLayer.mask = mask;
[self.layer addSublayer:myLayer];
This will crop the image appropriately, but the background color of the view is white and therefore still visible. I've tried making the other layers transparent, but it has not worked.
(self and subview both refer to same view)
[self layer].backgroundColor = [UIColor clearColor].CGColor //no change
[self layer].opacity = 0; //makes entire view transparent
subView.backgroundColor = [UIColor clearColor]; // entire view becomes transparent
Is it possible to create the effect I'm trying to achieve?
I tried the same thing, here is the exact code:
// In KMViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor lightGrayColor];
CGRect frame = CGRectMake(0.0f, 0.0f, 100.0f, 100.0f);
KMView *view = [[KMView alloc] initWithFrame:frame];
view.center = self.view.center;
[self.view addSubview:view];
}
// In KMView.m
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// self.backgroundColor = [UIColor clearColor];
CALayer *layer = [CALayer layer];
CAShapeLayer *mask = [CAShapeLayer layer];
layer.frame = self.bounds;
UIImage *image = [UIImage imageNamed:#"orange"];
layer.contents = (id)image.CGImage;
CGPathRef path = CGPathCreateWithEllipseInRect(frame, NULL);
mask.path = path;
CGPathRelease(path);
layer.mask = mask;
[self.layer addSublayer:layer];
}
return self;
}
It worked for me as expected. The background was transparent. Then I tried adding
self.backgroundColor = [UIColor clearColor];
to the beginning of the code and nothing changed, the image was showing on a transparent background clipped to the path.
I think the problem might be somewhere else.

iOS: Table cell footer dropshadow

I have a tableview with only a few rows. So instead of displaying a bunch of blanks I added a blank UIView to the tableview.footer. However I would like the last cell to cast a dropshadow on the UIView. How would I achieve this? Here is my current code.
- (void)viewDidLoad
{
[super viewDidLoad];
UIView *emptyView = [[UIView alloc] initWithFrame:CGRectZero];
CALayer *layer = [emptyView layer];
[layer setShadowOffset:CGSizeMake(0, 1)];
[layer setShadowColor:[[UIColor darkGrayColor] CGColor]];
[layer setShadowRadius:8.0];
[layer setShadowOpacity:0.8];
self.tableView.tableFooterView = emptyView;
}
EDIT:
It is adding the UIView to the footer but not creating the dropshadow. I'm not sure the layer is the best approach for this or even correct for this type of thing.
It is probably because you set the frame to CGRectZero.
The zero rectangle is equivalent to CGRectMake(0,0,0,0).
A shadow of a zero rect (x=0, y=0, width=0, height=0) won't show at all.
Try giving it a proper frame size and you will see the difference.
Check out this for ref as well: https://developer.apple.com/library/mac/#documentation/graphicsimaging/reference/CGGeometry/Reference/reference.html
I ended up using
shadowBackgroundView.layer.shadowOpacity = 0.3;
shadowBackgroundView.layer.shadowRadius = 2;
shadowBackgroundView.layer.shadowColor = [[UIColor blackColor] CGColor];
shadowBackgroundView.layer.shadowOffset = CGSizeMake(0.0, 1.0);
CGPathRef shadowPath = [UIBezierPath bezierPathWithRoundedRect: shadowBackgroundView.bounds
byRoundingCorners: UIRectCornerAllCorners
cornerRadii: CGSizeMake(PageCellBackgroundRadius, PageCellBackgroundRadius)].CGPath;
shadowBackgroundView.layer.shadowPath = shadowPath;
shadowBackgroundView.layer.shouldRasterize = YES;
[self addSubview: shadowBackgroundView];
In cellForRowAtIndexPath: function:
// drop shadow
cell.layer.shadowOpacity = 1.0;
cell.layer.shadowRadius = 1.7;
cell.layer.shadowColor = [UIColor blackColor].CGColor;
cell.layer.shadowOffset = CGSizeMake(0.0, 0.0);

UIView CAGradient with rounded corners?

I have an UIView with rounded corners and drop shadow, implemented and working. But the UIView has a boring background color, white. So I want to put a gradient layer as the background. Below the labels, buttons and most important, make it so the rounded corners still appears.
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = subHudView.bounds;
gradient.colors = [NSArray arrayWithObjects:(id)[[UIColor blackColor] CGColor], (id)[[UIColor whiteColor] CGColor], nil];
[subHudView.layer addSublayer:gradient];
subHudView.layer.cornerRadius = 8;
subHudView.layer.masksToBounds = NO;
subHudView.layer.shadowOffset = CGSizeMake(-5, 5);
subHudView.layer.shadowRadius = 8;
subHudView.layer.shadowOpacity = 0.75;
This is my code as I tried to implement it, but the gradient layer is on top of everything in the view know. How can I make the gradient go under all the controls and labels? Every responding help will be appreciated.
in Swift:
let gradientLayer = CAGradientLayer()
gradientLayer.cornerRadius = 20.0
A layer added to your view (addSublayer) is drawn in front of your view's own layer, which is where all your view's drawing takes place. What you want is to draw the gradient into your view's own layer. To do so, implement +layerClass in your view, specifying that you want your view's layer to be a gradient view.
So, for example (in the view's own code):
+(Class)layerClass {
return [CAGradientLayer class];
}
-(void)awakeFromNib {
[super awakeFromNib];
CAGradientLayer* layer = (CAGradientLayer*)self.layer;
layer.colors = [NSArray arrayWithObjects:(id)[[UIColor blackColor] CGColor], (id)[[UIColor whiteColor] CGColor], nil];
layer.cornerRadius = 8;
layer.masksToBounds = NO;
layer.shadowOffset = CGSizeMake(-5, 5);
layer.shadowRadius = 8;
layer.shadowOpacity = 0.75;
}
If you then implement drawRect:, however, you'll of course wipe out your gradient and your rounded corners. There are ways around that...
When you addSublayer: it add the layer at the top of all the sublayers.
You should probably use something like that instead :
[subHudView.layer insertSublayer:gradient atIndex:0];
That way, the CAGradientLayer will be below everything else.
As well as the insertSublayer:atIndex: that gcamp suggested, you can also use insertSublayer:above: and insertSublayer:below: to place your layer in specific places.
[self.view.layer insertSublayer:gradient below:someUIObject];
Rather than create a new layer, override your view's layer method:
+ (Class)layerClass
{
return [CAGradientLayer class];
}
This will ensure that your view create a CAGradientLayer, rather than a basic CALayer, as the base layer.
Then, during init get hold of the layer:
CAGradientLayer *gradLayer = self.layer;
gradLayer.colors = [NSArray arrayWithObjects:(id)[[UIColor blackColor] CGColor], (id)[[UIColor whiteColor] CGColor], nil];
and assign your gradients to it.
Nice and clean...