Линейные градиенты (а-ля CSS) в Swift
ПРИМЕЧАНИЕ. Поскольку на момент написания это было реализовано более полезным способом, я рекомендую использовать код, описанный в этом репозитории: https://github.com/nikolay-kapustin/CSSGradientToUIKit
Спасибо Николаю Капустину за улучшенный метод.
Если, как и я, вы переходите на iOS из Интернета, вам захочется как можно скорее погрузиться в градиенты. Не так быстро…
Градиенты в Apple SDK сильно отличаются от того, к чему мы привыкли в Интернете. Как и во многих вещах, есть еще несколько уровней сложности, но разве это не самое интересное ?!
Итак, мы собираемся попытаться воспроизвести здесь что-то, что мы сделали бы в CSS, давайте возьмем что-нибудь простое, например background-image: linear-gradient(47deg, magenta, cyan)
извините за использование именованных CSS color
s, но это достаточно близко к тому, что мы будем использовать в 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.