I currently have a tableView populated with the user's friends. The tableView is part of TableViewController which operates correctly. I want the tableview to be permanently faded at the very bottom similar to the following picture:
I'm aware that you can use gradients, but I'm not sure how. Somebody please help.
You could achieve this appearance by adding a -white background- UIView on top the the table view and set its alpha to the desired value.
At Storyboard, it would be similar to:
Note that at the document outline - views hierarchy the view is underneath the tableview, which means it would be on top of it.
Remark: it might be hard to add the view by dragging it directly to the view controller since it would be as subview in the table view; You could drag it in the document outline instead.
Then you could setup the desired constraints to it (I just added 0 leading, 0 trailing, 0 bottom and 70 height). Finally change its color opacity:
As you can see, I just edited the opacity to be 50% (alpha = 0.5).
Furthermore:
you could also let the bottom view to has gradient of white and clear colors, it could even make it nicer! You might want to check:
How to Apply Gradient to background view of iOS Swift App
You can do this by adding a gradient layer behind the button and above the table view.
For this, Create a UIView subclass, Say GradientView:
import UIKit
#IBDesignable
class GradientView: UIView {
#IBInspectable var startColor: UIColor = .black { didSet { updateColors() }}
#IBInspectable var middleColor: UIColor = .white { didSet { updateColors() }}
#IBInspectable var endColor: UIColor = .white { didSet { updateColors() }}
#IBInspectable var startLocation: Double = 0.05 { didSet { updateLocations() }}
#IBInspectable var middleLocation: Double = 0.05 { didSet { updateLocations() }}
#IBInspectable var endLocation: Double = 0.95 { didSet { updateLocations() }}
#IBInspectable var horizontalMode: Bool = false { didSet { updatePoints() }}
#IBInspectable var diagonalMode: Bool = false { didSet { updatePoints() }}
override class var layerClass: AnyClass {
return CAGradientLayer.self
}
var gradientLayer: CAGradientLayer {
return (layer as? CAGradientLayer)!
}
func updatePoints() {
if horizontalMode {
gradientLayer.startPoint = diagonalMode ? CGPoint(x: 1, y: 0): CGPoint(x: 0, y: 0.5)
gradientLayer.endPoint = diagonalMode ? CGPoint(x: 0, y: 1): CGPoint(x: 1, y: 0.5)
} else {
gradientLayer.startPoint = diagonalMode ? CGPoint(x: 0, y: 0): CGPoint(x: 0.5, y: 0)
gradientLayer.endPoint = diagonalMode ? CGPoint(x: 1, y: 1): CGPoint(x: 0.5, y: 1)
}
}
func updateLocations() {
gradientLayer.locations = [startLocation as NSNumber, middleLocation as NSNumber, endLocation as NSNumber]
}
func updateColors() {
gradientLayer.colors = [startColor.cgColor, middleColor.cgColor, endColor.cgColor]
}
override func layoutSubviews() {
super.layoutSubviews()
updatePoints()
updateLocations()
updateColors()
}
}
And, you can assign this view in storyboard as well as it is #IBDesignable:
You can configure the colour from here. In you case, you should take white colour:
Related
This question already has answers here:
How to Apply Gradient to background view of iOS Swift App
(30 answers)
Closed 3 years ago.
I am trying to set the background on my UIView to a gradient. I have the following in my UIViewController's viewDidLoad method:
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [UIColor.blue70.cgColor, UIColor.blue60.cgColor, UIColor.blue70.cgColor]
gradientLayer.startPoint = CGPoint(x: 0, y: 0)
gradientLayer.endPoint = CGPoint(x: 1, y: 1 )
gradientLayer.frame = self.view.bounds
self.view.layer.insertSublayer(gradientLayer, at: 0)
The gradient renders as expected. However, when I rotate the device the gradient does not redraw and is no longer rendered properly. Rotating from portrait->landscape leaves me with a blank section on the right or left. Rotating from landscape->portrait leaves me with a blank section at the bottom. I tried moving this code to viewDidLayoutSubviews, but that didn't solve it. Any recommendations on how to accomplish this?
What i would recommend here is to add the gradient in the viewWillLayoutSubviews() method
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
self.view.addGradiant()
}
But now we would have to remove the old layer When adding the new one with the new frames(In case the Phone rotates)
We can then first remove the layer and add the new one like in this extension method
extension UIView {
func addGradiant() {
let GradientLayerName = "gradientLayer"
if let oldlayer = self.layer.sublayers?.filter({$0.name == GradientLayerName}).first {
oldlayer.removeFromSuperlayer()
}
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [UIColor.blue.cgColor, UIColor.red.cgColor, UIColor.green.cgColor]
gradientLayer.startPoint = CGPoint(x: 0, y: 0)
gradientLayer.endPoint = CGPoint(x: 1, y: 1 )
gradientLayer.frame = self.bounds
gradientLayer.name = GradientLayerName
self.layer.insertSublayer(gradientLayer, at: 0)
}
}
Make gradientLayer view controller's property.
let gradientLayer = CAGradientLayer()
Add gradientLayer in the viewDidLoad in the same way you did.
Set the frame in viewWillLayoutSubviews
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
gradientLayer.frame = self.view.bounds
}
While you can do as the other answers suggest and update your gradient layer bounds in viewDidLayoutSubviews, the more reusable way to do things is to make a gradient view subclass who's layerClass is a CAGradientLayer. Such a view will have its layer automatically resized to fit the view (because the gradient layer is now the backing layer for the view and not some subLayer) and you can use this view anywhere just by changing the class of your view in your nib file or storyboard:
class GradientView: UIView {
var colors: [UIColor] = [.red, .white] {
didSet {
setup()
}
}
var locations: [CGFloat] = [0,1] {
didSet {
setup()
}
}
var startPoint: CGPoint = .zero {
didSet {
setup()
}
}
var endPoint: CGPoint = CGPoint(x: 1, y: 1) {
didSet {
setup()
}
}
override class var layerClass : AnyClass { return CAGradientLayer.self }
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override func awakeFromNib() {
super.awakeFromNib()
setup()
}
private func setup() {
guard let layer = self.layer as? CAGradientLayer else {
return
}
layer.colors = colors.map { $0.cgColor }
layer.locations = locations.map { NSNumber(value: Double($0)) }
layer.startPoint = startPoint
layer.endPoint = endPoint
}
}
This question already has answers here:
How to Apply Gradient to background view of iOS Swift App
(30 answers)
Closed 4 years ago.
used below code for adding gradient layer for a view. After adding gradient layer to a view if i tried to add new subviews inside my gradient view the new view are not getting displayed
func setGradientBackground(_ view: UIView ,colorStart:CGColor ,colorEnd:CGColor,cornerRadius:CGFloat) {
let gradientLayer = CAGradientLayer()
gradientLayer.colors = [colorStart, colorEnd]
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5);
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5);
gradientLayer.frame = view.bounds
gradientLayer.cornerRadius = cornerRadius
view.layer.addSublayer(gradientLayer)
}
if i extend a class with UIView how can i set gradient color directly from storyboard attributes inspector. I have seen this in some libraries (cosmos) where we can directly set rating color
Instead of adding gradient layer, create subclass of uiview and override layer property with gradientLayer
Gradient View:
#IBDesignable class VerticalGradientView: UIView {
#IBInspectable var topColor: UIColor = UIColor.red {
didSet {
setGradient()
}
}
#IBInspectable var bottomColor: UIColor = UIColor.blue {
didSet {
setGradient()
}
}
override class var layerClass: AnyClass {
return CAGradientLayer.self
}
override func layoutSubviews() {
super.layoutSubviews()
setGradient()
}
private func setGradient() {
(layer as! CAGradientLayer).colors = [topColor.cgColor, bottomColor.cgColor]
(layer as! CAGradientLayer).startPoint = CGPoint(x: 0.5, y: 0)
(layer as! CAGradientLayer).endPoint = CGPoint(x: 0.5, y: 1)
}
}
Use:
let gradientView = VerticalGradientView()
gradientView.frame = CGRect(0,0,100,100)
gradientView.topColor = .black
let label = UILabel()
gradientView.addSubview(label)
It looks like you are using the layer directly and adding it to the view's backing layer. I recommend a simple UIView subclass that encapsulates the gradient behavior. The implementation is quite simple and the the largest benefit is the control you get over where it is rendered in the z dimension. Then you can just use the gradient view as a parent view for your content or if that isn't reasonable in your specific scenario, you could just insert the gradient view as a subview at index 0 using the UIView.insert(_:,at:) method of UIView instances. This would make your gradient view appear below the other views you add later.
I recently began using gradients in one of my apps. Here is the code I used to define my custom gradient type.
import UIKit
public class AxialGradientView: UIView {
public var colors: [UIColor] = [] {
didSet {
gradientLayer.colors = colors.map { $0.cgColor }
}
}
/// Expressed in a unit coordinate system.
public var startPoint: CGPoint = CGPoint(x: 0.5, y: 0) {
didSet {
gradientLayer.startPoint = startPoint
}
}
/// Expressed in a unit coordinate system.
public var endPoint: CGPoint = CGPoint(x: 0.5, y: 1) {
didSet {
gradientLayer.endPoint = endPoint
}
}
override public class var layerClass: Swift.AnyClass {
get {
return CAGradientLayer.self
}
}
private var gradientLayer: CAGradientLayer {
return layer as! CAGradientLayer
}
}
I am searching for a long time on net. But no use. Please help or try to give some ideas how to achieve this.
create a UIViewX in the View Controller or
add UIButton on custom view(UIViewX). And IBAction is print(" test ")
Everything work good.
But...
When add UIButton on custom view(UIViewX) and add animation code with UIViewX, button does't work if I press it
how to fix it that can response print(" test ")?
Thanks in advance.
Here is animation code :
var currentColorArrayIndex = -1
var colorArray:[(color1: UIColor, color2: UIColor)] = []
override func viewDidLoad() {
super.viewDidLoad()
colorArrayFunction()
backgroundAnimate()
}
colorArrayFunction & backgroundAnimate
Here is UIViewX code :
import UIKit
#IBDesignable
class UIViewXXXXX: UIView {
// MARK: - Gradient
#IBInspectable var firstColor: UIColor = UIColor.white {
didSet {
updateView()}}
#IBInspectable var secondColor: UIColor = UIColor.white {
didSet {
updateView()}}
#IBInspectable var horizontalGradient: Bool = false {
didSet {
updateView()}}
override class var layerClass: AnyClass {
get {
return CAGradientLayer.self}}
func updateView() {
let layer = self.layer as! CAGradientLayer
layer.colors = [ firstColor.cgColor, secondColor.cgColor ]
if (horizontalGradient) {
layer.startPoint = CGPoint(x: 0.0, y: 0.5)
layer.endPoint = CGPoint(x: 1.0, y: 0.5)
} else {
layer.startPoint = CGPoint(x: 0, y: 0)
layer.endPoint = CGPoint(x: 0, y: 1)
}}
// MARK: - Border
#IBInspectable public var borderColor: UIColor = UIColor.clear {
didSet {
layer.borderColor = borderColor.cgColor}}
#IBInspectable public var borderWidth: CGFloat = 0 {
didSet {
layer.borderWidth = borderWidth}}
#IBInspectable public var cornerRadius: CGFloat = 0 {
didSet {
layer.cornerRadius = cornerRadius
}
}
// MARK: - Shadow
#IBInspectable public var shadowOpacity: CGFloat = 0 {
didSet {
layer.shadowOpacity = Float(shadowOpacity)}}
#IBInspectable public var shadowColor: UIColor = UIColor.clear {
didSet {
layer.shadowColor = shadowColor.cgColor}}
#IBInspectable public var shadowRadius: CGFloat = 0 {
didSet {
layer.shadowRadius = shadowRadius}}
#IBInspectable public var shadowOffsetY: CGFloat = 0 {
didSet {
layer.shadowOffset.height = shadowOffsetY}}}
I'm trying to apply a gradient to a view which is constraint to the top, left and right of the main screen but for some reason the gradient doesn't cover the whole width of the view that is applied to (see the yellow in the picture).
class ViewController: UIViewController {
#IBOutlet weak var myView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let gradient = CAGradientLayer()
gradient.colors = [UIColor.blue.cgColor, UIColor.white.cgColor]
gradient.startPoint = CGPoint(x:00, y:00)
gradient.endPoint = CGPoint(x:0, y:0.6)
gradient.frame = myView.bounds
myView.clipsToBounds = true
myView.layer.insertSublayer(gradient, at: 0)
}
}
What am I doing wrong?
The problem is likely that you are adding the gradient layer in viewDidLoad(). A view doesn't get set to it's final size until after viewDidLoad().
Your view controller may be set up in your XIB/Storyboard for a different screen-size than you're running it on. (Say you have it set to iPhone SE size, but you're running it on a 6. The 6's screen is a little wider, so the layer will get set to the width of the iPhone SE, when the view is first loaded. Then the view will be resized, but the layer's size will never be adjusted.)
You should implement the UIViewController method viewDidLayoutSubviews(), and in that method, adjust the layer's frame:
override func viewDidLayoutSubviews() {
gradientLayer.frame = self.view.bounds
}
That way if the view gets resized (due to auto-rotation, for example) the gradient layer will be automatically adjusted accordingly.
EDIT:
As pointed out by Sulthan, changes to a layer's frame are animated by default. You should probably wrap the frame change in a CATransaction.begin/CATransaction.end and disable actions, like below:
override func viewDidLayoutSubviews() {
CATransaction.begin()
CATransaction.setDisableActions(true)
gradientLayer.frame = self.view.bounds
CATransaction.commit()
}
You can turn it to a UIView. So it will resize automatically and can be seen directly in the Storyboard:
#IBDesignable
final class GradientView: UIView {
#IBInspectable var firstColor: UIColor = .clear { didSet { updateView() } }
#IBInspectable var secondColor: UIColor = .clear { didSet { updateView() } }
#IBInspectable var startPoint: CGPoint = CGPoint(x: 0, y: 0) { didSet { updateView() } }
#IBInspectable var endPoint: CGPoint = CGPoint(x: 1, y: 1) { didSet { updateView() } }
override class var layerClass: AnyClass { get { CAGradientLayer.self } }
override func layoutSubviews() {
super.layoutSubviews()
updateView()
layer.frame = bounds
}
private func updateView() {
let layer = self.layer as! CAGradientLayer
layer.colors = [firstColor, secondColor].map {$0.cgColor}
layer.startPoint = startPoint
layer.endPoint = endPoint
}
}
You needn't set the start and end point, given your goal is to have the gradient span the entire view. You're already setting that with `
gradientLayer.frame = self.view.bounds
`
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var myView: UIView!
var gradientLayer: CAGradientLayer!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
createGradientLayer()
}
func createGradientLayer() {
gradientLayer = CAGradientLayer()
gradientLayer.frame = self.view.bounds
gradientLayer.colors = [UIColor.blueColor().CGColor, UIColor.whiteColor().CGColor]
self.view.layer.addSublayer(gradientLayer)
}
}
simply put you gredient layer in main thred like this
DispatchQueue.main.async {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = self.viewGredient.bounds
gradientLayer.colors = [UIColor.black.cgColor, UIColor.white.cgColor]
gradientLayer.locations = [0.0, 1.0]
self.viewGredient.layer.addSublayer(gradientLayer)
}
You can simplify it by CustomClass with one line of code
class GradientView: UIView {
override class var layerClass: Swift.AnyClass {
return CAGradientLayer.self
}
}
// MARK: Usage
#IBOutlet weak var myView: GradientView!
guard let gradientLayer = myView.layer as? CAGradientLayer else { return }
gradient.colors = [UIColor.blue.cgColor, UIColor.white.cgColor]
gradient.startPoint = CGPoint(x: 0.0, y: 0.0)
gradient.endPoint = CGPoint(x:0, y:0.6)
I have a pretty strange problem with a project. XCode gives me red errors all the time and says that my IBDesignables have a problem. But the error message differs every time and it's always another view controller affected. I have no idea how to find the cause of the problem. It seems to happen randomly with all IBDesignables I created.
The app does still work. Although, sometimes it doesn't render some views correctly (I have views with a custom drawn mask). It happens rarely, but it does happen.
These are errors I get:
"Failed to update the auto layout status: The agent crashed"
"Failed to render and update the auto layout status for {randomly
chosen VC name}: The agent crashed"
The latter disappears as soon as I click on it. And sometimes, when I'm inside my storyboard, all of the errors go away. When I open any other file, I get multiple red errors (sometimes like 3, sometimes 6 or more).
I checked the crash logs. Every single one of them says:
fatal error: unexpectedly found nil while unwrapping an Optional value
I'm not sure what this means.
I tried cleaning the project and restarting XCode.
Also, I implemented all of the init methods.
This following code is a class that (almost) always is part of the errors I get. Also, it's the custom drawn view that sometimes doesn't render correctly:
import UIKit
struct TriangleViewLabelDefault {
static let triangleWidth: CGFloat = 20
static let triangleHeight: CGFloat = 10
}
#IBDesignable
class TriangleView: UIView {
let DEFAULT_TRIANGLE_WIDTH: CGFloat = 30
let DEFAULT_TRIAGNLE_HEIGHT: CGFloat = 20
struct Triangle {
static var width: CGFloat = TriangleViewLabelDefault.triangleWidth
static var height: CGFloat = TriangleViewLabelDefault.triangleHeight
static var midXPosition: CGFloat = 0
}
#IBInspectable var triangleWidth: CGFloat = TriangleViewLabelDefault.triangleWidth {
didSet {
Triangle.width = triangleWidth
}
}
#IBInspectable var triangleHeight: CGFloat = TriangleViewLabelDefault.triangleHeight {
didSet {
Triangle.height = triangleHeight
}
}
#IBInspectable var relativePosition: Bool = true {
didSet {
setNeedsDisplay()
}
}
#IBInspectable var triangleMidXOffset: CGFloat = 0 {
didSet {
setNeedsDisplay()
}
}
#IBInspectable var decoration: Bool = true {
didSet {
setNeedsDisplay()
}
}
#IBInspectable var lines: Int = 20 {
didSet {
setNeedsDisplay()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func draw(_ rect: CGRect) {
super.draw(rect)
calcTriangleMidXPosition()
addLines()
if decoration {
addDecoration()
}
let maskLayer = CAShapeLayer()
maskLayer.frame = bounds
let maskPath = UIBezierPath()
maskPath.move(to: CGPoint(x: 0, y: Triangle.height))
/* Triangle */
maskPath.addLine(to: CGPoint(x: Triangle.midXPosition - Triangle.width, y: Triangle.height))
maskPath.addLine(to: CGPoint(x: Triangle.midXPosition, y: 0))
maskPath.addLine(to: CGPoint(x: Triangle.midXPosition + Triangle.width, y: Triangle.height))
/* */
maskPath.addLine(to: CGPoint(x: bounds.size.width, y: Triangle.height))
maskPath.addLine(to: CGPoint(x: bounds.size.width, y: bounds.size.height))
maskPath.addLine(to: CGPoint(x: 0, y: bounds.size.height))
maskPath.addLine(to: CGPoint(x: 0, y: Triangle.height))
maskPath.close()
maskLayer.path = maskPath.cgPath
layer.mask = maskLayer
}
/// Evaluates the properties set in storyboard. This methods calculates the x-center of the triangle
private func calcTriangleMidXPosition() {
if relativePosition {
Triangle.midXPosition = bounds.size.width / 2 + triangleMidXOffset * bounds.size.width / 2
} else {
Triangle.midXPosition = bounds.size.width / 2 + triangleMidXOffset
}
}
private func addDecoration() {
if let superview = superview {
let topLine = UIView(frame: CGRect(x: 0, y: frame.minY + Triangle.height - 2, width: bounds.size.width, height: 1))
let bottomLine = UIView(frame: CGRect(x: 0, y: frame.minY + Triangle.height - 4, width: bounds.size.width, height: 1))
topLine.backgroundColor = UIColor.white
bottomLine.backgroundColor = UIColor.white
topLine.alpha = 0.5
bottomLine.alpha = 0.2
superview.insertSubview(topLine, belowSubview: self)
superview.insertSubview(bottomLine, belowSubview: self)
}
}
private func addLines() {
guard lines > 0 else {
return
}
let paddingLeft: Int = 20
let lineLength: Int = 25
let linesLayer = CAShapeLayer()
linesLayer.frame = bounds
let padding: CGFloat = 20
let minY = Triangle.height + padding
let maxY = bounds.height - padding
let yArea = maxY - minY
for x in 0...lines {
let path = UIBezierPath()
path.lineWidth = 3.0
path.move(to: CGPoint(x: CGFloat(paddingLeft), y: minY + CGFloat(x) * yArea / CGFloat(lines)))
path.addLine(to: CGPoint(x: CGFloat(paddingLeft) + CGFloat(lineLength), y: minY + CGFloat(x) * yArea / CGFloat(lines)))
UIColor(red: 200.0/255.0, green: 200.0/255.0, blue: 200.0/255.0, alpha: 1.0).setStroke()
path.stroke()
}
}
}
Thank you guys for any help!
Every time this "Agent crashed" issue occures, a crash log is created. You can find all of them here:
~/Library/Logs/DiagnosticReports
named IBDesignablesAgentCocoaTouch_*.crash
Just go into Finder (Go->Go to folder), copy the path and substitute with your username.
Probably you'll have too many of crash logs there, you can delete them all, clean your project, build again and check the folder.
If your code make you crash, you should found a debug button in the identify inspector as screenshot below. After you click the debug button, you can found out which statement make you crash.
I had the problem when creating a checkbox button, with 2 UIImage defined, for checked and unchecked.
Code did work well, but Agent crashed for IBDesignable (IBInspectable was OK)
I noticed several causes :
UIImages must not be created inside the class
I had to declare outside, with a property to hold the selected image, and initialise in init(coder):
import UIKit
let checkedImage = UIImage(named: "checked")!// as UIImage
let uncheckedImage = UIImage(named: "unchecked")!// as UIImage
#IBDesignable class CheckBox: UIButton {
#IBInspectable public var image: UIImage?
I had to remove the setImage in computed property
#IBInspectable public var isChecked: Bool = false { // this caused a crash
didSet{
if isChecked == true {
self.setImage(checkedImage, for: UIControl.State.normal)
} else {
self.setImage(uncheckedImage, for: UIControl.State.normal)
}
}
}
to just keep
#IBInspectable public var isChecked: Bool = false
Finally added a draw func
override func draw(_ rect: CGRect) {
image?.draw(in: rect)
}
If that may help others.