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
Related
I am having a hard time updating the UI on my Swift app while observing a Firebase node. I have a red_value, a green_value, and a blue_value stored in firebase which is being fetched and used to construct a UIColor which is then used to update the background color of a button on my UI. I am successfully fetching the color values, but when i try to update the UI from within the observe closure, the color always turns out to be white and I can't figure out why.
My code can be found below for a better idea of what i am trying to accomplish when loading the view:
override func viewDidLoad() {
super.viewDidLoad()
self.charitiesReference.child(charityUsername!).observe(.value) {
snapshot in
let values = snapshot.value as! [String: Any]
let redValue = values["red_value"] as! CGFloat
let greenValue = values["green_value"] as! CGFloat
let blueValue = values["blue_value"] as! CGFloat
let charityColor = UIColor(red: redValue, green: greenValue, blue: blueValue, alpha: 1.0)
let ripplerFollowers = values["follower_ripplers"] as! [String: Bool]
if ripplerFollowers["\(self.username!)"] ?? false {
self.followOrFollowingButton.setTitle("Following", for: .normal)
//ERROR -> The line below is producing a white background on my button
self.followOrFollowingButton.backgroundColor = charityColor
} else {
self.followOrFollowingButton.setTitle("Follow", for: .normal)
self.followOrFollowingButton.backgroundColor = .clear
}
}
}
Update:
When I change self.followOrFollowingButton.backgroundColor = charityColor line to self.followOrFollowingButton.backgroundColor = UIColor(red: 0, green: 120, blue: 120, alpha: 1.0)
it works as intended, which means that something is going on with the redValue, greenValue, & blueValue variables
Thank you in advance for your help!
As it turned out, I had forgotten to divide each of the color values by 255 to put them in the 0.0 - 1.0 range. if the value is above 1.0, it is set to 1.0, which resulted in the white color.
After dividing by 255, it's working like a charm
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.
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.
I have a function that creates a CGRect and I am trying to assign a random color to each of them.
I create the colors as variables with the type UIColor and then put them into an array called colors. Then, I create a random number generator and call it when defining the background color of the CGRect, but I get the error:
Cannot call value of non-function type "[UIColor}"
Why is this? Here is my code:
func addBox(location: CGRect) -> UIView {
let newBox = UIView(frame: location)
let red = UIColor(red: (242.0/255.0), green: (186.0/255.0), blue: (201.0/255.0), alpha: 1.0)
let green = UIColor(red: (186.0/255.0), green: (242.0/255.0), blue: (216.0/255.0), alpha: 1.0)
let yellow = UIColor(red: (242.0/255.0), green: (226.0/255.0), blue: (186.0/255.0), alpha: 1.0)
let blue = UIColor(red: (186.0/255.0), green: (216.0/255.0), blue: (242.0/255.0), alpha: 1.0)
let colors = [red, green, yellow, blue]
let randomNum:UInt32 = arc4random_uniform(4)
newBox.backgroundColor = UIColor(colors(randomNum))
hView.insertSubview(newBox, at: 0)
return newBox
}
If anyone could solve this that would be amazing. Any help would be immensely appreciated!! Thanks a ton in advance.
This:
newBox.backgroundColor = UIColor(colors(randomNum))
should be:
newBox.backgroundColor = colors[randomNum]
colors is an array of UIColor. You just need one element from the array.
You should also change:
let randomNum:UInt32 = arc4random_uniform(4)
to:
let randomNum = Int(arc4random_uniform(colors.count))
This way if you add more colors to the array, you don't need to adjust this line. It makes your code less error prone.
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.