CAGradientLayer, not resizing nicely, tearing on rotation SWIFT - ios

So I'm working on a iOS application written in Swift and I came across this exact same issue as this:
CAGradientLayer not resizing nicely.
I add a CAGradientLayer to my UIView and I override willAnimateRotationToInterfaceOrientation in order to update the gradient bounds. This only seems to update AFTER the rotation is complete and thus you see the bounds of the original frame when rotating (which causes the user to see the white background of my app).
Here is my code:
in viewDidLoad, I have the following code:
var color1: UIColor = UIColor(red: CGFloat(0.12), green: CGFloat(0.13), blue: CGFloat(0.70), alpha: CGFloat(1.0))
var color2: UIColor = UIColor(red: CGFloat(0.00), green: CGFloat(0.59), blue: CGFloat(1.00), alpha: CGFloat(1.0))
var color3: UIColor = UIColor(red: CGFloat(0.00), green: CGFloat(1.00), blue: CGFloat(1.00), alpha: CGFloat(1.0))
let gradient : CAGradientLayer = CAGradientLayer()
let arrayColors: [AnyObject] = [color1.CGColor, color2.CGColor, color3.CGColor]
gradient.frame = self.view.bounds
gradient.colors = arrayColors
view.layer.insertSublayer(gradient, atIndex: 0)
and I override this function:
override func willAnimateRotationToInterfaceOrientation(toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) {
var gradient:CAGradientLayer = self.view.layer.sublayers[0] as CAGradientLayer
gradient.frame = self.view.bounds
}
The solution is seems to be this:
// GradientView.h
#interface GradientView : UIView
#property (nonatomic, strong, readonly) CAGradientLayer *layer;
#end
// GradientView.m
#implementation GradientView
+ (Class)layerClass {
return [CAGradientLayer class];
}
#end
But I'm having a really hard time converting this class to Swift. Can someone help me? So far I have this:
import Foundation
import UIKit
class GradientView:UIView
{
var gradientLayer:CAGradientLayer = CAGradientLayer()
func layerClass() -> CAGradientLayer {
return gradientLayer.self
}
}
But it doesn't seem to work. I'm not understanding what would be the Swift equivalent of the 'class' message being used in [CAGradientLayer class]?
I've been trying to make this work for a few hours now and I just can't seem to get it. Any help would be highly appreciated!
Thanks

You need to override the layerClass() class method, and return a class:
class GradientView: UIView {
override class func layerClass() -> AnyClass {
return CAGradientLayer.self
}
}
At some point, you will also want to set your gradientLayer property to self.layer.

Related

How to properly generate a random gradient background?

I've been trying to create a function that generates a random gradient background every time a new view controller is presented, as shown with the code provided below, yet I'm unable to get it to properly work. The issue being that the "random" colors are always the same and are generated in the same order.
import Foundation
import UIKit
extension UIView {
func setGradientBackground() {
let redValue = CGFloat(drand48())
let greenValue = CGFloat(drand48())
let blueValue = CGFloat(drand48())
let redValue2 = CGFloat(drand48())
let greenValue2 = CGFloat(drand48())
let blueValue2 = CGFloat(drand48())
let random = UIColor(red: redValue, green: greenValue, blue: blueValue, alpha: 1).cgColor
let random2 = UIColor(red: redValue2, green: greenValue2, blue: blueValue2, alpha: 1).cgColor
let gradient = CAGradientLayer()
gradient.frame = bounds
gradient.colors = [random, random2]
return layer.insertSublayer(gradient, at: 0)
}
}
If you use drand48, you need to seed it (e.g. with srand48). The quick and dirty solution would be to seed it with some value derived from time from the number of seconds passed since some reference date or the like.
Even better, I’d suggest using CGFloat.random(in: 0...1) instead, which doesn’t require any seeding.
extension UIView {
func addGradientBackground() {
let random = UIColor.random().cgColor
let random2 = UIColor.random().cgColor
let gradient = CAGradientLayer()
gradient.frame = bounds
gradient.colors = [random, random2]
layer.insertSublayer(gradient, at: 0)
}
}
extension UIColor {
static func random() -> UIColor {
let red = CGFloat.random(in: 0...1)
let green = CGFloat.random(in: 0...1)
let blue = CGFloat.random(in: 0...1)
return UIColor(red: red, green: green, blue: blue, alpha: 1)
}
}
For what it’s worth, we should appreciate that this technique of adding a sublayer is problematic if the view changes size (e.g., you rotate the device, etc.). You’d have to manually hook into viewDidLayoutSubviews or layoutSubviews and adjust this gradient layer frame. I might instead suggest actually having a UIView subclass that does this gradient layer.
#IBDesignable
class RandomGradientView: UIView {
override static var layerClass: AnyClass { return CAGradientLayer.self }
var gradientLayer: CAGradientLayer { return layer as! CAGradientLayer }
override init(frame: CGRect = .zero) {
super.init(frame: frame)
configure()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
gradientLayer.colors = [UIColor.blue.cgColor, UIColor.red.cgColor]
}
}
private extension RandomGradientView {
func configure() {
let random = UIColor.random().cgColor
let random2 = UIColor.random().cgColor
gradientLayer.colors = [random, random2]
}
}
Because this UIView subclass actually specifies the layer class to be used for the main backing layer, and because that main layer always resizes automatically as the view changes size, then it will gracefully handle any size changes (including animated ones).
All of the having been said, I appreciate the convenience of the extension (you can apply it to existing UIView subclasses), but you have to offset that with the more cumbersome process of handling frame size changes. It’s your choice, but I wanted to provide the alternative.

Change the colors of CAGradientLayer with slider in realtime

I want to be able to change to change the colours of a CAGradientLayer in realtime with values i receive over #IBAction in the main ViewController. UISlider moves, color changes in background. I m asking for a theoretical approach.
What is the pattern or technique used to achieve this ? i really got lost and the mighty internet didn't reveal any usable help.
Subclasssing a UIView and add the gradientLayer there ? Then observe the variable with the incoming values and let KVO update the array entries (color sets) with cgColor values ?
Instantiate the gradientLayer in the main ViewController and update its color Properties there when value changes come in via #IBAction ?
Code helps, but its secondary here. i am asking more for a theoretical solution. I try to follow MVC but i am hardly confused where the gradientLayer should be instantiated, whats the best method to change the colours dynamically ect…
open for inputs, thx
Using a very simple extension for UIView you can get what you need. Here is a quick example using semi-static colors just for simplicity, but you can extend it as you need.
extension UIView {
/**
Adds colors to a CAGradient Layer.
- Parameter value: The Float coming from the slider.
- Parameter gradientLayer: The CAGradientLayer to change the colors.
*/
func addColorGradient (value: CGFloat, gradientLayer: CAGradientLayer) {
let topColor = UIColor(red: value / 255, green: 1, blue: 1, alpha: 1)
let bottomColor = UIColor(red: 1, green: 1, blue: value / 255, alpha: 1)
gradientLayer.colors = [topColor.cgColor, bottomColor.cgColor]
}
}
Usage example:
import UIKit
class ViewController: UIViewController {
let slider : UISlider = {
let slider = UISlider(frame: .zero)
slider.maximumValue = 255
slider.minimumValue = 0.0
return slider
}()
var gradientLayer = CAGradientLayer()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(slider)
slider.translatesAutoresizingMaskIntoConstraints = false
slider.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
slider.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
slider.widthAnchor.constraint(equalToConstant: 200).isActive = true
slider.heightAnchor.constraint(equalToConstant: 20).isActive = true
slider.addTarget(self, action: #selector(sliderValueChanged), for: .valueChanged)
gradientLayer.bounds = self.view.bounds
self.view.layer.insertSublayer(gradientLayer, at: 0)
}
#objc func sliderValueChanged(sender: UISlider) {
let currentValue = CGFloat(sender.value)
self.view.addColorGradient(value: currentValue, gradientLayer: gradientLayer)
}
}
Just change the topColor and bottomColor as you need and manipulate their values with the slider. Hope it helps
class ViewController: UIViewController {
var gradientLayer = CAGradientLayer()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
gradientLayer.bounds = self.view.bounds
self.view.layer.insertSublayer(gradientLayer, at: 0)
}
// Slider Input
#IBAction func sliderValueChanged(_ sender: UISlider) {
self.view.addColorGradient(value: CGFloat(sender.value), gradientLayer: gradientLayer)
}
}

ImageView Gradient behaving abnormally

I have gradient(clear - black) applied to an imageview and on top of it I have a float button. Everything works fine. The only issue is, whenever there is a tap on float button, the gradient start increasing to black more and more. my gradient is clear to black from top to bottom. But on interaction, It start to slowely blacken towards upside.
I am really unable to solve this error.
This image view is in a UIcollectionResuableView. Below is the code for yhe following.
func addBlackGradientLayerprof(frame: CGRect){
let gradient = CAGradientLayer()
gradient.frame = frame
let black = UIColor.init(red: 0/255, green: 0/255, blue: 0/255, alpha: 0.65).cgColor
gradient.colors = [UIColor.clear.cgColor, black]
gradient.locations = [0.0, 1.0]
self.layer.addSublayer(gradient)
}
Header View with floating button:
override func layoutSubviews() {
super.layoutSubviews()
profilePic.addBlackGradientLayerprof(frame: profilePic.bounds)
}
override func awakeFromNib() {
super.awakeFromNib()
layoutFAB()
}
func layoutFAB() {
floaty.openAnimationType = .slideDown
floaty.addItem("New Post", icon: UIImage(named: "photo-camera")) { item in
self.delegate.fabUploadClicked()
}
floaty.addItem("Settings", icon: UIImage(named: "settingsB")) { item in
self.delegate.fabSettingsClicked()
}
floaty.paddingY = (frame.height - 30) - floaty.frame.height/2
floaty.fabDelegate = self
floaty.buttonColor = UIColor.white
floaty.hasShadow = true
floaty.size = 45
addSubview(floaty)
}
layoutSubviews is bad place to do anything other than adjust a few frames as needed. It can be called many times in the lifecycle of a view. You should not be adding layers from layoutSubviews.
You should call addBlackGradientLayerprof from a place guaranteed to only be called once in the lifetime of the object. awakeFromNib would be one possible place.

IOS Background Gradients - Storyboard

Is there anyway to show background gradients on the storyboard instead of firing up the simulator every-time to see what it looks like
I've created and extension
import UIKit
extension CAGradientLayer {
func stopwatchColour() -> CAGradientLayer {
let topColour = UIColor(red: (11/255.0), green: (128/255.0), blue: (105/255.0), alpha: 1)
let bottomColour = UIColor(red: (39/255.0), green: (90/255.0), blue: (78/255.0), alpha: 1)
let gradientColours: [CGColor] = [topColour.cgColor, bottomColour.cgColor]
let gradientLocations: [Float] = [0.0, 1.0]
let gradientLayer: CAGradientLayer = CAGradientLayer()
gradientLayer.colors = gradientColours
gradientLayer.locations = gradientLocations as [NSNumber]
return gradientLayer
}
}
Then from my controller
let background = CAGradientLayer().stopwatchColour()
background.frame = self.view.bounds
self.view.layer.insertSublayer(background, at: 0)
I recommend trying the code in a swift playground. Swift Playground executes your code every time you type. You can see what your background looks like and edit it however you wish. Then you can paste your code into your Xcode project without having to run the simulator.
Good luck

How to access extension of UIColor in Swift?

I am very new to swift and trying to create an extension of UIColor class as
extension UIColor{
func getCustomBlueColor() -> UIColor {
return UIColor(red:0.043, green:0.576 ,blue:0.588 , alpha:1.00)
}
}
After this I accessed the method as
btnShare.setTitleColor(UIColor.getCustomBlueColor(**UIColor**), forState: UIControlState.Normal)
I don't know what I should pass as an argument to this statement.
You have defined an instance method, which means that you can call
it only on an UIColor instance:
let col = UIColor().getCustomBlueColor()
// or in your case:
btnShare.setTitleColor(UIColor().getCustomBlueColor(), forState: .Normal)
The compiler error "missing argument" occurs because
Instance Methods are Curried Functions in Swift,
so it could equivalently be called as
let col = UIColor.getCustomBlueColor(UIColor())()
(But that would be a strange thing to do, and I have added it only to
explain where the error message comes from.)
But what you really want is a type method (class func)
extension UIColor{
class func getCustomBlueColor() -> UIColor{
return UIColor(red:0.043, green:0.576 ,blue:0.588 , alpha:1.00)
}
}
which is called as
let col = UIColor.getCustomBlueColor()
// or in your case:
btnShare.setTitleColor(UIColor.getCustomBlueColor(), forState: .Normal)
without the need to create an UIColor instance first.
With Swift 3, predefined UIColors are used accordingly:
var myColor: UIColor = .white // or .clear or whatever
Therefore, if you want something similar, such as the following...
var myColor: UIColor = .myCustomColor
...then, you would define the extension like so:
extension UIColor {
public class var myCustomColor: UIColor {
return UIColor(red: 248/255, green: 248/255, blue: 248/255, alpha: 1.0)
}
}
In fact, Apple defines white as:
public class var white: UIColor
Swift 3, Swift 4, Swift 5:
extension UIColor {
static let myBlue = UIColor(red:0.043, green:0.576 ,blue:0.588, alpha:1.00)
}
Use:
btnShare.setTitleColor(.myBlue, for: .normal)
Or
self.view.backgroundColor = .myBlue
If you use Color Set in *.xcassets (iOS11+).
For example, you have a color with the name «appBlue». Then:
extension UIColor {
private static func getColorForName(_ colorName: String) -> UIColor {
UIColor(named: colorName) ?? UIColor.red
}
static var appBlue: UIColor {
self.getColorForName("appBlue")
}
}
Use:
self.view.backgroundColor = .appBlue
Get the this extension for customize type UIView
extension UIColor {
// Method returns a custom color
static func rgb(red: CGFloat, green: CGFloat, blue: CGFloat) -> UIColor {
return .init(red: blue / 255, green: green / 255, blue: blue / 255, alpha: 1.0)
}
}
You just need to change your statement like,
btnShare.setTitleColor(UIColor().getCustomBlueColor(), forState:.Normal)
More detailed explanation is here.
Could use a computed property:
extension UIColor {
var customBlueColor: UIColor {
return UIColor(red:0.043, green:0.576 ,blue:0.588 , alpha:1.00)
}
}
And then to call it:
UIColor().customBlueColor
You defined a instance function. It means you need an instance of UIColor in case to use getCustomBlueColor()-method.
It looks like you want to have a class method, instead of the instance method. So you have to change your definition like this:
extension UIColor{
class func getCustomBlueColor() -> UIColor{
return UIColor(red:0.043, green:0.576 ,blue:0.588 , alpha:1.00)
}
}
Note the 'class' before func, so the method is now accessible as a class method.
The same story using class methods in a structure:
struct MyColors{
static func getCustomBlueColor() -> UIColor{
return UIColor(red:0.043, green:0.576 ,blue:0.588 , alpha:1.00)
}
}
let color = MyColors.getCustomBlueColor()
If you just want to have a class with some color definitions, I recommend you to use a struct over a class or extension:
struct MyColors{
static var getCustomBlueColor = { return UIColor(red:0.043, green:0.576 ,blue:0.588 , alpha:1.00) }
}
let color = MyColors.getCustomBlueColor()
UIColor Extension Swift 5
extension UIColor {
static var yourColor:UIColor {
return UIColor(red: 0.745, green: 0.157, blue: 0.074, alpha: 1)
}
}
Use :
view.backgroundColor = .yourColor
Swift:
extension UIColor {
open class var yourOrange: UIColor {
return UIColor.init(colorLiteralRed: 0.988, green: 0.337, blue: 0.063, alpha: 1)
}
}

Resources