Линейные градиенты (а-ля CSS) в Swift

ПРИМЕЧАНИЕ. Поскольку на момент написания это было реализовано более полезным способом, я рекомендую использовать код, описанный в этом репозитории: https://github.com/nikolay-kapustin/CSSGradientToUIKit

Спасибо Николаю Капустину за улучшенный метод.

Если, как и я, вы переходите на iOS из Интернета, вам захочется как можно скорее погрузиться в градиенты. Не так быстро…

Градиенты в Apple SDK сильно отличаются от того, к чему мы привыкли в Интернете. Как и во многих вещах, есть еще несколько уровней сложности, но разве это не самое интересное ?!

Итак, мы собираемся попытаться воспроизвести здесь что-то, что мы сделали бы в CSS, давайте возьмем что-нибудь простое, например background-image: linear-gradient(47deg, magenta, cyan) извините за использование именованных CSS colors, но это достаточно близко к тому, что мы будем использовать в Swift с UIColor.

Если бы я добавил это к <div>, мы получили бы что-то вроде этого:

Теперь это достаточно просто в CSS, это в основном простой английский, как и в большинстве современных CSS.

В Swift мы добавляем градиенты, добавляя CAGradientLayer, в основном слой, который ОС рисует там, где он нужен для создания градиента, вы не можете применить градиент в качестве фона, как вы привыкли в Интернете и / или в Sketch или Figma. . Однако вы можете переместить этот слой в самый конец вашего представления, и он, по сути, будет вашим фоном, пока он будет такими же .width и .height, что и ваш содержащий UIView.

Теперь давайте посмотрим, как я могу воспроизвести этот градиент без лишних наворотов:

import UIKit
// Set Up a Test View in Swift Playgrounds
  let testView = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
// Create Gradient Layer
  var gradientLayer = CAGradientLayer()
// Set Layer Size
  gradientLayer.frame = testView.frame
// Provide an Array of CGColors
  gradientLayer.colors = [UIColor.magenta.cgColor, UIColor.cyan.cgColor]
// Guesstimate a Match to 47° in Coordinates
  gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.95)
  gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.05)
// Add Gradient Layer to Test View
  testView.layer.addSublayer(gradientLayer)

Не стесняйтесь попробовать это на детской площадке, вы можете поиграть с разными цветами и начальными / конечными точками. Результаты примерно такие:

Чертовски близко к той версии CSS, которая у нас была раньше. Сложность здесь заключается в переходе от градусов 47deg к координатам .startpoint = CGPoint(x:0.0, y:0.95) .endpoint = CGPoint(x:1.0, y: 0.05), а они даже не на 100% точны, просто около того. Чтобы определить реальную точку, вы можете представить квадрат, разделенный пополам с прямой линией:

Разве не было бы замечательно, если бы мы могли проработать эти непонятные моменты с помощью Swift, чтобы мы могли просто подавать под углом в deg, как мы привыкли? Почему да, было бы; Математически мы можем превратить любое значение в градусах, например, Int в .startpoint и .endpoint, это требует некоторой тригонометрии уровня GCSE. Сначала мы смотрим на то, что мы знаем, мы знаем, где будут лежать начальные и конечные координаты для всех кратных 90 ° и 45 °, потому что они должны приземлиться на углу или на биссектрисе оси, поэтому мы получаем следующее:

Итак, теперь нам нужно использовать немного больше математики, я не буду здесь вдаваться в подробности, я не репетитор по математике, но вот пример математики, связанной с нашим примером с углом 47 °:

Поскольку мы используем tan, мы можем рассчитать только определенные углы и получить правильный результат, поэтому логика градиента должна выполняться отдельно для каждого квадранта. Кроме того, стоит отметить, что мы можем вычислить .endpoint координаты, используя простые математические вычисления, как только у нас будет .startpoint вычислено, это должно немного улучшить наш код.

Давайте сделаем быструю функцию, чтобы разобраться с противоположными моментами, которые мы можем использовать позже:

extension CGPoint {
  func opposite() -> CGPoint {
    // Create New Point
    var oppositePoint = CGPoint()
    // Get Origin Data
    let originXValue = self.x
    let originYValue = self.y
    // Convert Points and Update New Point
    oppositePoint.x = 1.0 - originXValue
    oppositePoint.y = 1.0 - originYValue
    return oppositePoint
  }
}

Теперь, когда это сделано, мы можем объединить это с предыдущими математическими выкладками. Перед тем, как мы начнем использовать tan() в Swift, нужно учесть одну вещь: функция принимает значения в радианах, а не в градусах, в которые мы должны их преобразовать, поэтому я расширил для этого тип Int, но есть и другие методы, разбавленные по вкусу:

extension Int {
  func degreesToRads() -> Double {
    return (Double(self) * .pi / 180)
  }
}

Хорошо, мы готовы создать нашу функцию. Я использую здесь очень длинный switch оператор, дайте мне знать, есть ли лучший способ, потому что он не кажется идеальным (помощь в комментариях будет признательна), на данный момент это работает для меня.

func startAndEndPointsFrom(angle: Int) -> (startPoint:CGPoint, endPoint:CGPoint) {
// Set default points for angle of 0°
  var startPointX:CGFloat = 0.5
  var startPointY:CGFloat = 1.0

// Define point objects
  var startPoint:CGPoint
  var endPoint:CGPoint

// Define points
  switch true {
// Define known points
  case angle == 0:
    startPointX = 0.5
    startPointY = 1.0
  case angle == 45:
    startPointX = 0.0
    startPointY = 1.0
  case angle == 90:
    startPointX = 0.0
    startPointY = 0.5
  case angle == 135:
    startPointX = 0.0
    startPointY = 0.0
  case angle == 180:
    startPointX = 0.5
    startPointY = 0.0
  case angle == 225:
    startPointX = 1.0
    startPointY = 0.0
  case angle == 270:
    startPointX = 1.0
    startPointY = 0.5
  case angle == 315:
    startPointX = 1.0
    startPointY = 1.0
// Define calculated points
  case angle > 315 || angle < 45:
    startPointX = 0.5 - CGFloat(tan(angle.degreesToRads()) * 0.5)
    startPointY = 1.0
  case angle > 45 && angle < 135:
    startPointX = 0.0
    startPointY = 0.5 + CGFloat(tan(90.degreesToRads() - angle.degreesToRads()) * 0.5)
  case angle > 135 && angle < 225:
    startPointX = 0.5 - CGFloat(tan(180.degreesToRads() - angle.degreesToRads()) * 0.5)
startPointY = 0.0
  case angle > 225 && angle < 359:
    startPointX = 1.0
    startPointY = 0.5 - CGFloat(tan(270.degreesToRads() - angle.degreesToRads()) * 0.5)
  default: break
  }
// Build return CGPoints
  startPoint = CGPoint(x: startPointX, y: startPointY)
  endPoint = startPoint.opposite()
// Return CGPoints
  return (startPoint, endPoint)
}

Все готово, теперь мы можем создать от extension до UIView, чтобы предоставить нам ту же функциональность, что и background-image: linear-gradient(47deg, magenta, cyan), обратите внимание:

extension UIView {
  func linearGradientBackground(angleInDegs: Int, colors: [CGColor]) {
    // Create New Gradient Layer
      let gradientBaseLayer: CAGradientLayer = CAGradientLayer()
    // Feed in Our Parameters
      gradientBaseLayer.frame = self.frame
      gradientBaseLayer.colors = colors
      gradientBaseLayer.startPoint = startAndEndPointsFrom(angle: angleInDegs).startPoint
      gradientBaseLayer.endPoint = startAndEndPointsFrom(angle: angleInDegs).endPoint
    // Add Our Gradient Layer to the Background
      self.layer.insertSublayer(gradientBaseLayer, at: 0)
  }
}

Теперь мы можем использовать наш прекрасный новый метод в любом UIView! Представьте себе возможности.

Вот наш конечный продукт:

// Set an Array Containing Any Number of CGColors
let testColors:[CGColor] = [UIColor.magenta.cgColor, UIColor.cyan.cgColor]
// It's Practically CSS!
view.linearGradientBackground(angleInDegs: 47, colors: testColors)

Стоит отметить, что этот пример дает результаты типа CSS только для идеально квадратных объектов, для любого другого размера UIView он будет создавать градиент с углом, который будет казаться немного скошенным или растянутым по сравнению с его аналогом CSS, но он должен быть достаточно полезным для большинство приложений.

А вот фотографии, подтверждающие, что все это работает:

Эта работа является частью проекта, над которым я сейчас работаю, под названием градиенты, позволяющий дизайнерам экспериментировать, находить красивые градиенты и экспортировать их для использования на целом ряде устройств. с экспортируемым, пригодным для использования кодом. Если вы хотите сотрудничать со мной или хотите получить какие-либо функции, дайте мне знать.

Я также буду искать дизайнеров для включения в приложение, с пространством для размещения тщательно подобранных градиентов и образцов цвета. Если вы хотите, чтобы вас представили в приложении, дайте мне знать, и я расскажу вам. в списке.

Обо мне

Меня зовут Дэвид Тодд. Я дизайнер и разработчик из Северо-Восточной Англии. Вы можете подписаться на меня в Twitter или Instagram.