7

I would like to create a gradient layer with four given colours. I have a sketch file for the gradients. I have the coordinates for the colour stops. I calculated the normalised coordinates for the start and the end point for the CAGradientLayer. I also calculated the location where the colours change each other. Still the layer does not match with the sketch image. Can you help me what could be wrong? Usage of the exported image is not an option because the layer has to change with animation to other gradient locations.

let imageWidth: CGFloat = 375.0
let imageHeihgt: CGFloat = 293.0

class ViewController: UIViewController {

    let homeColors: [UIColor] = [UIColor(red: 62/255, green: 131/255, blue: 255/255, alpha: 1.0),
                                 UIColor(red: 99/255, green: 22/255, blue: 203/255, alpha: 1.0),
                                 UIColor(red: 122/255, green: 5/255, blue: 239/255, alpha: 1.0),
                                 UIColor(red: 122/255, green: 5/255, blue: 240/255, alpha: 1.0)]

    let startPoint: CGPoint = CGPoint(x: -0.225/imageWidth*UIScreen.main.bounds.width, y: -0.582*imageHeihgt/UIScreen.main.bounds.height)
    let endPoint: CGPoint = CGPoint(x: 1.088/imageWidth*UIScreen.main.bounds.width, y: 1.01*imageHeihgt/UIScreen.main.bounds.height)

    let locations: [NSNumber] = [0.0, 0.734, 0.962, 1.0]

    var subview: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()
        subview = UIView(frame: CGRect(x: 0, y: 20, width: imageWidth, height: imageHeihgt))
        view.addSubview(subview)
        view.sendSubview(toBack: subview)
        addGradientLayer()
    }

    func addGradientLayer() {
        let gradient = CAGradientLayer()
        gradient.frame = subview.bounds
        gradient.colors = homeColors.map { $0.cgColor }
        gradient.startPoint = startPoint
        gradient.endPoint = endPoint
        gradient.locations = locations
        subview.layer.insertSublayer(gradient, at: 0)
    }
}

I have attached some screenshots to make the difference clear. It is not a big difference but still. SideBySide Sketch

Gergely
  • 448
  • 3
  • 13
  • it looks like the endpoints for the CAGradientLayer do not match the ones in sketch. I'm not well educated on the CAGradientLayer to tell you if this is possible or not to move them outside the bounds of the layer – yano Oct 09 '17 at 18:32
  • https://github.com/Gradients/Gradients – SPatel Jan 17 '20 at 06:06
  • https://github.com/rwbutler/AnimatedGradientView – SPatel Jan 17 '20 at 06:06

2 Answers2

0

Sketch renders graphics differently than iOS. Even if you match the colors and locations perfectly, the end result will still look different.

If you want an exact match to the Sketch file, you'll need to make some optical adjustments to the colors to account for the differences. Your two images are very close, so with some minor color adjustments, it should look a lot closer.

Try making your light blue (top left) and light purple (bottom right) less vibrant. I don't have access to your Sketch file, so I can't suggest new colors to get an exact match, but it shouldn't be too hard, just a little trial and error.

nathangitter
  • 9,607
  • 3
  • 33
  • 42
  • How can I make these adjustments? Just with experimenting? Should exist some other way – Gergely Oct 09 '17 at 18:39
  • @Gergely Yeah just try some new colors until it looks good. I would start with the current color of interest (say the light blue in the top left), and go into Sketch and make the color slightly less vibrant (move the color down and to the left in the color picker box). See if it works, and make adjustments from there. At this point, choosing the colors is an art more than a science. – nathangitter Oct 09 '17 at 18:42
  • Ironically I cannot achieve that :D It just get worse when I make any changes by hand.. – Gergely Oct 09 '17 at 20:16
  • @Gergely The changes are going to be very small. If moving the color on the Sketch color picker isn't working, try starting with the original color and using the rainbow slider below to get a slightly darker hue. Again, these are really small adjustments so you shouldn't need to move the color too much. – nathangitter Oct 09 '17 at 20:24
0

I wrote a script convert Sketch gradient layer to CALayer. Insert this layer by view.layer.insertSublayer(gradientLayer, at: 0).

Link: Convert Sketch Gradient To Swift Code

Select a layer which contains one gradient fill, then press control + shift + K, paste code below and run. you will see:

Convert Result

And on iPhone it looks like:

On Real Device

console.log('Select Layer Then Run Script.');

var sketch = require('sketch');
var UI = require('sketch/ui');

var document = sketch.getSelectedDocument();

var selectedLayers = document.selectedLayers;
var selectedCount = selectedLayers.length;

function hexToRGBA(hex) {
  var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  return result ? {
    r: parseInt(result[1], 16),
    g: parseInt(result[2], 16),
    b: parseInt(result[3], 16),
    a: parseInt(result[4], 16)
  } : null;
}

let precesion = 10000

function parseGradient(layerGradient) {

  let colorItems = ""
  let locationArray = ""

  layerGradient.stops.forEach(elm => {
    let rgbaColor = hexToRGBA(elm.color)
    colorItems += `UIColor(red: ${rgbaColor.r}/255.0, green: ${rgbaColor.g}/255.0, blue: ${rgbaColor.b}/255.0, alpha: ${rgbaColor.a}/255.0), `
    locationArray += `${Math.round(elm.position*precesion)/precesion}, `
  });

  var codeTemplate = `
import Foundation
import UIKit

class LinearGradientLayer: CALayer {
    
  required override init() {
      super.init()
      needsDisplayOnBoundsChange = true
  }
  
  required init?(coder aDecoder: NSCoder) {
      super.init(coder: aDecoder)
  }
  
  required override init(layer: Any) {
      super.init(layer: layer)
  }
  
  let colors = [${colorItems}].map(\{ \$0.cgColor \})
  
  override func draw(in ctx: CGContext) {
      ctx.saveGState()
      let colorSpace = CGColorSpaceCreateDeviceRGB()
      let locations: [CGFloat] = [${locationArray}]
      let gradient = CGGradient(colorsSpace: colorSpace, colors: colors as CFArray, locations: locations)!
      
      ctx.drawLinearGradient(gradient, start: CGPoint(x: ${Math.round(layerGradient.from.x*precesion)/precesion}*bounds.width, y: ${Math.round(layerGradient.from.y*precesion)/precesion}*bounds.height), end: CGPoint(x: ${Math.round(layerGradient.to.x*precesion)/precesion}*bounds.width, y: ${Math.round(layerGradient.to.y*precesion)/precesion}*bounds.height), options: [.drawsBeforeStartLocation, .drawsAfterEndLocation])
  }
}
`;

  return codeTemplate
}

if (selectedCount === 0) {
  UI.alert('No layers are selected', 'Select one gradient filled layer and rerun.')
} else {
  selectedLayers.forEach(function (layer, i) {
    console.log((i + 1) + '. ' + layer.name);
    let layerGradient = layer.style.fills[0].gradient;

    console.log(layerGradient)

    UI.getInputFromUser(
      "Convert Result",
      {
        initialValue: parseGradient(layerGradient),
        numberOfLines: 12
      },
      (err, value) => {
        if (err) {
          // most likely the user canceled the input
          return;
        }
      }
    );

  });
}


Roger Lee
  • 200
  • 1
  • 7