48

For my application I'm using a TableView and using customized UITableViewCells.

I customized my cells via interface builder, not programmatically. Is there a way to also make the background color of my customized cell a gradient in the interface builder?

Thanks.

Aberrant
  • 3,423
  • 1
  • 27
  • 38
Serdar Dogruyol
  • 5,147
  • 3
  • 24
  • 32

9 Answers9

64

This works for Swift 3.0: (Updated for Swift 4.0)

@IBDesignable
final class GradientView: UIView {
    @IBInspectable var startColor: UIColor = UIColor.clear
    @IBInspectable var endColor: UIColor = UIColor.clear

    override func draw(_ rect: CGRect) {
        let gradient: CAGradientLayer = CAGradientLayer()
        gradient.frame = CGRect(x: CGFloat(0),
                                y: CGFloat(0),
                                width: superview!.frame.size.width,
                                height: superview!.frame.size.height)
        gradient.colors = [startColor.cgColor, endColor.cgColor]
        gradient.zPosition = -1
        layer.addSublayer(gradient)
    }
}

enter image description here

enter image description here

nayem
  • 7,285
  • 1
  • 33
  • 51
etayluz
  • 15,920
  • 23
  • 106
  • 151
  • 4
    This puts the gradient layer on top of you subviews which is obviously not desirable, use `gradient.zposition = -1` to fix that. – Kashif Dec 13 '16 at 20:07
  • 1
    This is awesome thank you! I have never seen anything like this before and will be looking into making more custom view classes from now on. – Eli Byers May 01 '17 at 18:34
  • 3
    Might be a matter of the use case, but using the superview's frame was not what I needed. Changing it to `gradient.frame = bounds` gave me the expected behaviour. – Jerrot Jul 21 '17 at 14:17
  • Is it a way to see in StoryBoard how it looks? – János Dec 01 '17 at 16:08
  • How to set the gradient angle in this one? Thank you in advance – Curiosity Dec 04 '17 at 09:59
  • Please take a look at my adapted solution below. I think it is much better to override the layerClass property to avoid adding and maintaining a separate layer – JulianM Dec 15 '17 at 16:14
30

To draw a gradient, you will have to subclass and override the drawRect programmatically:

- (void)drawRect:(CGRect)rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();

    CGContextSaveGState(context);
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGGradientRef gradient = CGGradientCreateWithColorComponents
                             (colorSpace,
                              (const CGFloat[8]){1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f},
                              (const CGFloat[2]){0.0f,1.0f},
                              2);

    CGContextDrawLinearGradient(context,
                                gradient,
                                CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMinY(self.bounds)),
                                CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMaxY(self.bounds)),
                                0);

    CGColorSpaceRelease(colorSpace);
    CGContextRestoreGState(context);
}

The easiest way, which keeps your cells in the interface builder, is probably to subclass a UIView to have it draw a gradient in its drawRect and place it in your cell behind the other subviews:

GradientView *gradientView = [[GradientView alloc] init];
gradientView.frame = cell.bounds;
[cell addSubview:gradientView];
[cell sendSubviewToBack:gradientView];

However, the best way to do it is probably not to use the interface builder for this and make a subclass of UITableViewCell. For advanced customization, interface builders tend to only make things more complicated in my experience. That's up to personal preference though.

Aberrant
  • 3,423
  • 1
  • 27
  • 38
  • @SerdarDogruyol For the first solution I mentioned, UIView. For the second, UITableViewCell. I suggest reading the UIView class reference for more information on UIView subclassing and drawing: http://developer.apple.com/library/ios/#documentation/uikit/reference/uiview_class/uiview/uiview.html – Aberrant Nov 17 '11 at 13:37
  • What is the format of the array of floats? There are 8 of them, I assume this is some custom way to set the gradient colors, but I can't figure it out. – TheJeff Dec 08 '16 at 19:43
  • 1
    @TheJeff The 8 floats represent two colours: red , green, blue, alpha, red, green, blue, alpha. – Aberrant Dec 12 '16 at 15:19
  • How could this be the correct answer if it doesn't address the question's requirement for interface builder? The question is asking how to do this in interface builder and this answer is actually refusing to address that. – etayluz Feb 21 '18 at 06:17
  • This question is over 6 years old, as far as I was aware there was no way to do this in interface builder without custom classes. I don't know if newer versions do support it. And I imagine OP probably doesn't keep track of this question for up-to-date answers. – Aberrant Mar 02 '18 at 15:28
13
CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = yourView.bounds;
gradient.colors = [NSArray arrayWithObjects:(id)[[UIColor blackColor] CGColor], (id)    [[UIColor whiteColor] CGColor], nil];
[yourView.layer insertSublayer:gradient atIndex:0];
Ali Raza
  • 2,796
  • 1
  • 20
  • 24
7

Yes this is possible : Make a image in gradient with 1 X Height pix. Set this to backgroundColor for cell.

cell.backgroundColor =  [UIColor colorWithPatternImage:[UIImage imageNamed:@"gradImage.png"]];

**You can set gradient color with code but its time taken process. If you fill better then search for that.

Mick MacCallum
  • 129,200
  • 40
  • 280
  • 281
Sudesh Kumar
  • 569
  • 3
  • 13
  • I believe this is the best answer: "Is there a way...in the interface builder", but you should have provided a link on how to make the gradient. – TigerCoding Nov 17 '11 at 20:45
  • Note, though, that due to the retina displays, images are likely to look ugly (pixelated) compared to the other components when they don't exactly match the unpredictable resolution of the device. – Aberrant Nov 18 '11 at 09:22
  • 1
    @Aberrant, there is nothing unpredictable about resolutions due to retina displays. So long as you follow the `@2x` pattern of providing both images, you'll see no blurriness. – Kirk Woll Aug 21 '13 at 15:27
  • @KirkWoll My apologies, the retina display is indeed unrelated to this. What I should've said is "due to the unpredictable pixel resolution". It's essentially not possible to know for sure whether your 100 px image is going to be displayed on 100, 200, 437 or whichever number of pixels. But indeed, this is not a specific issue with retina displays, but with screen-relative image sizes on unpredictable resolutions in general. – Aberrant May 20 '14 at 13:40
6

answer by etayluz works fine but I added a couple changes:

  1. Gradient size defined by own layer bounds, not the superview.
  2. Remove Gradient layer on every draw, so that it does not keep drawing and adding a new layer when redraw is necessary (for instance by calling .setNeedsDisplay() on rotation).

    @IBDesignable final class MFGradientView: UIView {
    
        @IBInspectable var startColor: UIColor = UIColor.clear
        @IBInspectable var endColor: UIColor = UIColor.clear
    
        override func draw(_ rect: CGRect) {
           layer.sublayers?.first?.removeFromSuperlayer()
    
           let gradient: CAGradientLayer = CAGradientLayer()
           gradient.frame = CGRect(origin: CGPoint.zero, size: self.bounds.size)
           gradient.colors = [startColor.cgColor, endColor.cgColor]
           layer.insertSublayer(gradient, at: 0)
        }
    } 
    
alfhern
  • 311
  • 4
  • 8
4

Based on etayluz answer I changed the code a little bit by taking the layerClass property of a UIView into account, so you do not need a separate layer as a sublayer.

I think it is much cleaner and it also works with live updates in the Interface Builder.

@IBDesignable final class GradientView: UIView {

    @IBInspectable var startColor: UIColor = UIColor.red
    @IBInspectable var endColor: UIColor = UIColor.blue

    override class var layerClass: AnyClass {
        get {
            return CAGradientLayer.self
        }
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupGradient()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupGradient()
    }

    private func setupGradient() {
        let gradient = self.layer as! CAGradientLayer
        gradient.colors = [startColor.cgColor, endColor.cgColor]
    }

}
JulianM
  • 2,540
  • 18
  • 13
  • How do you add this to a project? I've created a GradientView class with your code, but specifying that file as the Custom Class (and specifying colors in IB) doesn't apply a gradient; I just see a white background and setupGradient doesn't get called. – Mike Miller Mar 29 '18 at 04:59
1

Create a simple view class with @IBInspectable properties.

  1. Create gradient layer once
  2. Reuse gradient layer each layout subviews

...

//
//  GradientView.swift
//
//  Created by Maksim Vialykh on 23.08.2018.
//  Copyright © 2018 Vialyx. All rights reserved.
//

import UIKit

class GradientView: UIView {

    @IBInspectable
    var startColor: UIColor = .white

    @IBInspectable
    var endColor: UIColor = .black

    private let gradientLayerName = "Gradient"

    override func layoutSubviews() {
        super.layoutSubviews()

        setupGradient()
    }

    private func setupGradient() {
        var gradient: CAGradientLayer? = layer.sublayers?.first { $0.name == gradientLayerName } as? CAGradientLayer
        if gradient == nil {
            gradient = CAGradientLayer()
            gradient?.name = gradientLayerName
            layer.addSublayer(gradient!)
        }
        gradient?.frame = bounds
        gradient?.colors = [startColor.cgColor, endColor.cgColor]
        gradient?.zPosition = -1
    }

}
LLIAJLbHOu
  • 1,313
  • 12
  • 17
-1

I don't know if there is a gradient option, but you could add an UIImageView to the custom cell and add an image with a gradient.

TigerCoding
  • 8,710
  • 10
  • 47
  • 72
-8

No, you can't. You could use UISegmentedControl with one segment in older sdk and xCode versions: http://chris-software.com/index.php/2009/05/13/creating-a-nice-glass-buttons/ But now you can't make less than two segments in one UISegmentedControl. And even this way you couldn't change the default colors of the buttons without coding.

Gargolev
  • 43
  • 7