css with a confidence inspiring comment
background: radial-gradient(423.17% 112.48% at 32.67% 115.21%, #FFD3D3 7.4%, #E7B9B9 24.14%, #596EBB 58.01%, #382A8B 96.1%) /* warning: gradient uses a rotation that is not supported by CSS and may not behave as expected */;
relevant part of an svg when the screen is exported:
Which yielded this abomination of a code with startpoint and endpoint I have picked off the wall:
static func shouldMakeGradientLayer(frame: CGRect? = nil) -> CAGradientLayer {
let gradientLayer = CAGradientLayer()
// Define colors
gradientLayer.colors = [
UIColor(hex: 0xFFD3D3).cgColor,
UIColor(hex: 0xE7B9B9).cgColor,
UIColor(hex: 0x596EBB).cgColor,
UIColor(hex: 0x382A8B).cgColor
].reversed()
// Define locations
//background: radial-gradient(423.17% 112.48% at 32.67% 115.21%, #FFD3D3 7.4%, #E7B9B9 24.14%, #596EBB 58.01%, #382A8B 96.1%) /* warning: gradient uses a rotation that is not supported by CSS and may not behave as expected */;
// gradientLayer.locations = [0.0739577, 0.241399, 0.580144, 0.961]
// make a guess :-[ gradientLayer.locations = [0, 0.8, 0.9, 1
// Apply the gradient transform
gradientLayer.type = .radial
// let transform = CATransform3DMakeRotation(-71.2512 * (.pi / 180), 0, 0, 1)
guard let w = frame?.width, let h = frame?.height else {
return gradientLayer
}
// gradientLayer.transform = CATransform3DConcat(CATransform3DMakeScale(964.534/w, 1675.8/w, 1), transform)
// gradientLayer.position = CGPoint(x: 122.5, y: 935.5)
gradientLayer.transform = CATransform3DMakeScale(964.534/w, 1675.8/h, 1)
let startPoint: CGPoint = CGPoint(x: 1, y: 0)
let endPoint: CGPoint = CGPoint(x: -2.2, y: 1.4)
gradientLayer.startPoint = startPoint
gradientLayer.endPoint = endPoint
if let frame = frame {
gradientLayer.frame = frame
}
return gradientLayer
}
Question #1: Is there an easier way to get radial gradient ? or Question #2: Should I have the UI designer isolate the parts of svg that I need as a background and I bite the bullet adding UIImageView as a background in each and every uiviewcontroller that needs it (30+ screens)?
maybe there is some straighforward way to map CSS percentages to locations array?
UPD: Youtube was prompt to suggest to me https://www.youtube.com/watch?v=I4gUvhG7uFU to get me to hate css a little less. But the disclaimer still stands is that I don;t understand css and could use a hand here.
UPD: what needs to be achieved (ignore the icons and the rest of the cruft)
Linear CAGradientLayer
...
(0, 0)
(top-left)(0, 1)
(bottom-left)and we get a top-down gradient.
Radial CAGradientLayer
...
set start point... for example, (0.5, 0.5)
(center)
set end point... for example, (0, 0)
or (1, 1)
or (0, 1)
or (1, 0)
doesn't matter
set n-number of colors, which by default are "spaced" evenly
and we get a Radial gradient starting from the center.
The general idea -- we start with a radial gradient:
Then position it relative to the view frame:
and it looks similar to this:
For your design, we can set the start point to:
x: 0.0, y: 1.25
that will put the "center point" of the radial at the left-edge, and 125% of the height (25% past the bottom).
Then we can set the end point to:
x: 3.5, y: 0.0
That puts the "outer edge" of the radial at 350% of the width (so, 250% past the right edge), and at the top.
To make things easier on ourselves, we'll create a UIView
subclass, with a few "settable" properties:
class RadialGradientView: UIView {
public var colors: [UIColor] = [] {
didSet {
gradLayer.colors = colors.compactMap( { $0.cgColor } )
}
}
public var locationPcts: [Double] = [] {
didSet {
gradLayer.locations = locationPcts.compactMap( { NSNumber(floatLiteral: $0 / 100.0) } )
}
}
public var start: CGPoint = .init(x: 0.5, y: 0.5) {
didSet {
gradLayer.startPoint = start
}
}
public var end: CGPoint = .init(x: 1.0, y: 1.0) {
didSet {
gradLayer.endPoint = end
}
}
override class var layerClass: AnyClass { CAGradientLayer.self }
private var gradLayer: CAGradientLayer { layer as! CAGradientLayer }
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
gradLayer.type = .radial
// defaults, just so we have something to see if colors is not set
self.start = .init(x: 0.5, y: 0.5)
self.end = .init(x: 1.0, y: 1.0)
colors = [.red, .green, .blue, .yellow]
// leave locations at default
}
}
If we create a 300x300 instance of that view and add it as a centered subview - with no other settings:
class SampleViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
NSLayoutConstraint.activate([
v.widthAnchor.constraint(equalToConstant: 300.0),
v.heightAnchor.constraint(equalTo: v.widthAnchor),
v.centerXAnchor.constraint(equalTo: view.centerXAnchor),
v.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
}
}
it will look like this:
If we set some custom colors:
let v = RadialGradientView()
v.colors = [
UIColor(hex: 0xFFD3D3),
UIColor(hex: 0xE7B9B9),
UIColor(hex: 0x596EBB),
UIColor(hex: 0x382A8B),
]
we get this:
and custom start/end points:
let v = RadialGradientView()
v.colors = [
UIColor(hex: 0xFFD3D3),
UIColor(hex: 0xE7B9B9),
UIColor(hex: 0x596EBB),
UIColor(hex: 0x382A8B),
]
v.start = .init(x: 0.0, y: 1.25)
v.end = .init(x: 3.5, y: 0.0)
We're close to what we want:
The next step is to use that as the background of the view controller... of course, probably multiple controllers. And, we don't want to have to add and constrain that as a subview every time.
So, let's subclass UIViewController
:
class MyCustomBackgroundViewController: UIViewController {
override func loadView() {
let v = RadialGradientView()
v.colors = [
UIColor(hex: 0xFFD3D3),
UIColor(hex: 0xE7B9B9),
UIColor(hex: 0x596EBB),
UIColor(hex: 0x382A8B),
]
// here you can to "tweak" the start, end and locations values
// to get your desired appearance
v.start = .init(x: 0.0, y: 1.25)
v.end = .init(x: 3.5, y: 0.0)
//v.locationPcts = [7.4, 24.14, 58.01, 96.1]
self.view = v
}
}
and, use that class instead of UIViewController
for all of our controllers:
class AnotherViewController: MyCustomBackgroundViewController {
override func viewDidLoad() {
super.viewDidLoad()
// everything normally as with any UIViewController
let imgView = UIImageView()
imgView.contentMode = .scaleAspectFit
imgView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(imgView)
NSLayoutConstraint.activate([
imgView.widthAnchor.constraint(equalToConstant: 160.0),
imgView.heightAnchor.constraint(equalTo: imgView.widthAnchor),
imgView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
imgView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
if let img = UIImage(systemName: "photo") {
imgView.image = img
imgView.tintColor = .white
}
}
}
and (hopefully) we're on our way: